美文网首页
关于递归的一点理解

关于递归的一点理解

作者: YocnZhao | 来源:发表于2019-07-08 17:05 被阅读0次

    什么是递归,一幅图表示:


    递归是穷举的一种方式。本篇文章并不涉及递归定义的讲解,只是本人对递归的一点小理解。
    结论:部分递归可以归纳成一个N叉树状的逻辑结构
    比如这个题目:
    x个苹果,一天只能吃一个、两个、或者三个,问多少天可以吃完

    拿到这问题第一时间知道这肯定要用递归来解决,但是总觉着无处下手。
    这个时候我们不妨找一个我们最熟悉的案例来过一下递归的结构,那我们掏出BinaryTree的DFS

         public void visitNode(TreeNode node, Bean bean) {
            if (node == null) {
                return;
            }
            bean.num++;
            visitNode(node.left, bean);
            visitNode(node.right, bean);
        }
    

    发现其实思想是类似的,一个是递归左右子树,一个是递归三种情况。那是不是说这个题目也可以同样的用二叉树或者N叉树的结构表现出来呢?


    那大致就长这个样子,我们要做的就是用DFS遍历这棵三叉树。

    1. 节点表示的是每天吃了一个两个还是三个苹果,
    2. 树的高度表示我们花了多少次循环完成找到的结果。
    3. 叶子节点表明递归条件终止

    其实我们自己去想的时候也是这样子,列出所有的集合,然后找到符合条件的情况。只不过树状结构比较直观,列出了所有的情况。

    代码如下所示:

    public class XApple {
        public void test() {
            eatApple(3, 0, "");
        }
    
        private void eatApple(int x, int day, String result) {
            eatAppleEveryDay(x, 1, day, result);
            eatAppleEveryDay(x, 2, day, result);
            eatAppleEveryDay(x, 3, day, result);
        }
    
        /**
         * @param left     还剩苹果个数
         * @param everyDay 每天吃的个数
         * @param day      无意义,打印用,第几天
         * @param result   无意义,打印用,结果传递
         */
        private void eatAppleEveryDay(int left, int everyDay, int day, String result) {
            result = result + "第" + day + "天吃" + everyDay + "个|";
            left -= everyDay;
    
            if (left < 0) {
                return;
            }
            if (left == 0) {
                System.out.print(result + "\n");
                return;
            }
            eatApple(left, ++day, result);
        }
    }
    

    虽然有的递归可以抽象成N叉树的方式来加深理解,比如全排列,N皇后,汉诺塔等经典递归问题(这里又涉及到回溯)。
    但是有一些需要用递归的就无法抽象成N叉树的样子,比如斐波那契数列,阶乘等就不好抽象,所以还是有局限性。

    我们直观上看这几个方法是顺序执行的:

            eatAppleEveryDay(x, 1, day, result);
            eatAppleEveryDay(x, 2, day, result);
            eatAppleEveryDay(x, 3, day, result);
    

    就有时候总会有一种错觉,就是这几个方法会按照123的顺序来顺序执行,吃一个总是在最前面,那岂不是每次都需要先吃一个苹果才会走后面的2跟3。
    那我们再简化一下,4个苹果,每天吃一个或者两个,我们去掉打印需要,那代码如下所示。

        public void test() {
            eat(4, 0);
        }
    
        private void eat(int x, int day) {
            eatPerDay(x, 1, day);
            eatPerDay(x, 2, day);
        }
    
        private void eatPerDay(int left, int everyDay, int day) {
            left -= everyDay;
            if (left <= 0) {
                return;
            }
            eat(left, ++day);
        }
    

    我们自己来想,总会有一个答案是第一天吃两个,第二天吃两个。也就是执行了两次eatPerDay(x, 2, day);, 嗯?这个2-2是怎么来的呢?
    为什么没有执行吃一个的代码也就是eatPerDay(x, 1, day);?怎么可能不执行?
    不执行第一句是怎么执行到第二句的?

    最浅显容易理解的答案是1-1-1-1,也就是4天每天吃1个。这个打到条件return之后会执行1-1-1-2,当然这个是不满足条件的,那这个是怎么执行过去的呢?越来越迷糊,感觉对程序运行产生了怀疑。
    再退一步,如果我们什么都不干预,只需要执行4层,是会产生2^4=16中答案的,我们从中挑出我们想要的摒弃掉不想要的,树状结构就是这么来的,树状结构只是结构,并不代表执行的次数只会执行一次。
    如果我们把需要执行4层的所有的结果都不加控制的输出也就是从左往右深度遍历二叉树:
    1-1-1-1
    1-1-1-2
    1-1-2-1
    ...
    2-2-1-1
    ...
    2-2-2-2
    一共16条结果,这其中2-2-1-1其实是我们需要的结果,只不过我们只需要它的主干,后面的1-1不需要所以直接就不往后执行了,并不是2-2凭空开始执行的。

    这里写一下加深理解,自己每次看到这个XApple也会懵一阵子,好记性不如烂笔头,写下来好了。

    相关文章

      网友评论

          本文标题:关于递归的一点理解

          本文链接:https://www.haomeiwen.com/subject/cjqahctx.html