散装JS - 梅开二度:探索递归结构

35 阅读3分钟

上一篇我们通过argument的callee初尝了10阶乘的递归结构(链接),这一次我们来深入了解一下js中包含的递归结构。

此次我们仍然用10!来讨论递归和循环的结构。首先我们回顾一下循环的结构:

        //for循环求和
        var sum=0;       
        for(var i=10; i>=1; i--){                  
                sum=sum+i;     
        }    
        document.write(sum);
        
        //do-while循环求和
        var i = 10;
        do{
            sum=sum+i;
            i--;
        }while(i>=1)
        
        //while循环求和
        var i = 10;
        while(i>=1){
            sum=sum+i;
            i--;
        }

而递归结构:

       function sum(i) {
            if (i == 1) {
	         return 1;         //递归最深层的1;
	    } else {
	         return i * sum( i - 1);       //递归中的i*(i - 1)
	    }
        }
        console.log(sum(10));

解析递归与循环

循环和递归在执行时的逻辑结构是不同的。
循环的逻辑是分步执行:算完i=10,再算i=9;算完i=9再算i=8...
递归的逻辑是层级执行:算i=10需要先算出i=9;算i=9需要先算出i=8...一直到算出i=2需要先算出i=1,而i=1时在if中已经定义好了return的1,那么接下来只需要从i=1再得到i=2...一直返回到最外层的i=10.

微信截图_20230410213628.png

递归的优势在于树状结构计算时,比线性分步结构的循环要计算量小很多:

微信截图_20230410213717.png

试想树状结构,如果是循环结构,需要一条一条计算分支的结果,将会比从底层计算到顶的递归增加多少计算量。

堆栈模型解释递归

堆栈的数据结构模型可以很好地用于解释递归(也可以反过来说递归结构可以帮助理解堆栈的数据结构)。

堆(英语:Heap)是计算机科学中的一种树状数据结构,需要满足下层数据向高层数据之间存在递进关系。栈(stack)是另一种数据结构,它的特性在于LIFO(Last In - First Out),想象它像一个桶,在桶最上层的是最晚放进去的数据,但是却是最先被调取出来的。
以简单的堆栈模型来理解递归,好比当我们已知要计算i=n的值时,就需要将这一数据先放入桶里,然后依次递进,直到达到递归界限i=1(假设),i=1是最上层的值,但是最上层的值却是最易得到的。然后为了获得i=n,我们再依次将桶里的每一层i取出,直到取得i=n。

微信截图_20230410221646.png

实现递归的argument.callee作用

当我们写一个函数递归,且不用callee时:

    function loop(i){
        var sum=0;
        if (i>10) {
            return sum;
        }
        sum = sum + i;
        loop(i+1);       //注意这里,在函数中引用函数自己实现递归。
        }
    loop (0);

在上面的代码中,函数名出现在了函数定义内形成了紧密耦合。当函数名出现变化,就必须修改从内到外的函数名称;或我们定义的是匿名函数的情况下,无法直接调用函数实现递归,这种情况下我们就需要使用callee。 但callee是个“昂贵”的操作,在每次迭代时会重新创建,将增加运行负担,需要酌情使用。

部分参考

计算机世界里的“堆栈”你真的懂吗? - 华清远见的文章 - 知乎 zhuanlan.zhihu.com/p/259807976

什么是堆? 什么是栈? - 白鲸鱼的文章 - 知乎 zhuanlan.zhihu.com/p/101531768

blog.csdn.net/czh500/arti…