宏观角度认识递归之汉诺塔问题

宏观角度认识递归 

听到递归,不少人都会对此充满些迷惑,今天我们就从不同的角度来认识递归,消除恐惧。

递归,简单来说,就是函数自己调用自己的情况,也就是在一个主问题中,我们会引申出一个相同的子问题,子问题继续引申下去,还是一个相同的子问题; 

此处以二叉树中的后序遍历来借以理解:要先对根结点的左节点和右节点依次遍历,然后再遍历自身节点;

1. 将 1 作为根结点,依次遍历其左子树和右子树,再遍历 1 ;

2. 将 2 作为根结点,依次遍历其左子树和右子树,再遍历 2 ;

3. 将 4 作为根结点,依次遍历其左子树和右子树,再遍历 4 ;

......

类似于这种情况,每一个子问题,都是与主问题类似的,也就是每个子问题的执行方法都是相同的,就可以运用到递归了;

宏观角度认识递归之汉诺塔问题_第1张图片

认识了递归后,我们要学会从另外一个角度来看递归:宏观角度看待递归的过程,以往的情况我们可能都会去画图来帮助理解,这也是一种好办法,但有时候会局限于这个思维;

所谓的宏观角度看待递归可以理解为三个点:

1. 不要太在意递归的细节展开图;

2. 把递归的函数当成一个黑盒;

3. 要坚信这个黑盒函数一定可以完成任务;

利用宏观角度思想来写递归:

1. 找到相同的子问题 -> 定义出函数头;

2. 针对一个子问题来理解是如何解决的 -> 定义出函数体;

3. 针对函数的出口做一下出口,避免死循环;

利用这个思想,对于二叉树的后序遍历来写一个伪代码:

1. 对于二叉树的后序遍历,它的主问题和子问题都是对于一个根结点,进行左子树遍历,然后右子树遍历,最后根结点自身遍历,因此这个问题就需要到了根结点的位置,才有办法去找到左右子树的位置从而去遍历,所以函数头可以定义为:void dfs(TreeNode* root);

2. 通过上述对于子问题的描述,我们就要先对左子树遍历,然后右子树遍历,最后遍历自身节点,因此函数体可以这样来定义:dfs(root->left); dfs(root->right); printf(root-val); 此处就体现了宏观思想了,要对这个函数当成一个黑盒的,不用去过于在意它的细节展开;

3. 最后要考虑函数的出口,也就是当节点为 null 的时候,要直接return,因此添加条件:

if(root == null) return;

 所以伪代码可以写为:

void dfs(TreeNode* root)
{
    if(root == null) return;
    dfs(root ->left);
    dfs(root -> right);
    printf(root->val);
}

 汉诺塔问题

面试题 08.06. 汉诺塔问题 - 力扣(LeetCode)

先考虑圆盘为 1,2 两种情况进行分析:当圆盘数量为 1 的时候,可以直接放到 C 柱子上;

当圆盘数量为 2 的时候,就需要借助到 B 柱子来完成摆放了; 

宏观角度认识递归之汉诺塔问题_第2张图片

再考虑圆盘为 3 的情况进行分析:当圆盘数量为 3 的时候,观察下图,我们可以看出具体的流程:先将上面的两个盘子借助 C柱子 摆放到 B柱子上,然后最大的盘子再到 C 柱子上,最后 B 柱子上的两个盘子,再借助于 A 柱子回到 C 柱子上;

宏观角度认识递归之汉诺塔问题_第3张图片

此时仔细思考,三个圆盘的时候, 要对最顶上的两个圆盘进行放置于 B 位置,这个操作就相当于当圆盘为 2 的时候的操作了。此时主问题的解决办法就和子问题的解决方法形成相似的情况了,也就是可以使用递归了。

因此,当有 n 个圆盘的时候,统一的解决办法都是如此:

1. A柱子上的 n-1 个圆盘借助于 C柱子,放到 B柱子上;

2. A柱子底下最大的圆盘放到 C柱子上;

3. B柱子上的圆盘借助于 A柱子放到 C柱子上;

1. 针对重复的子问题,定义函数头:

bfs(List A, List B, List C,int n)

A,B,C 集合依次表示A集合借助于B集合移动到C集合上 + 针对A集合上n个圆盘进行移动;

2. 针对子问题进行理解,定义函数体:

 2.1 将 A 上的 n-1 个圆盘借助于 C 移到 B 上:bfs(A,C,B,n-1);

 2.2 将 A 上的最后一个圆盘移到 C 上;

 2.3 将 B上的 n-1 个圆盘借助于 A 移到 C上:bfs(B,A,C,n-1);

3. 考虑出口的设计:

此时就要考虑圆盘数为 1 时的处理方式:将 A 上的圆盘直接放置于 C 上; 

完成代码 

class Solution {
    public void hanota(List A, List B, List C) {
        bfs(A,B,C,A.size());
    }
    public void bfs(List A, List B, List C,int n){

        // 出口设计,当只有一个圆盘的时候,直接从 A 移动到 C
        if(n == 1){
            C.add(A.remove( A.size() - 1 ));
            return;
        }

        bfs(A,C,B,n-1);     // 1. 将 A 上的 n-1 个圆盘借助 C 放置 B 上

        // 注意此处 A 的最后一个圆盘不能直接写 [0]
        // 因为在递归的过程中,针对的是某个子问题
        C.add(A.remove( A.size() - 1 ));     // 2. 将 A 上的最后一个圆盘,放到 C 上

        bfs(B,A,C,n-1);     // 3. 将 B 上的 n-1 个圆盘借助 A 放到 C 上

    }
}

你可能感兴趣的:(递归,算法,数据结构,递归)