递归与循环优劣 + 递归算法详解

todo

以下是递归与循环的优缺点:有点难以理解,仅做参考


递归的话函数调用是有开销的,而且递归的次数受堆栈大小的限制。 以二叉树搜索为例: bool search(btree
* p, int v) { if (null == p) return false; if (v == p->v) return true else { if (v < p->v) return search(p->left, v); else return search(p->right, v); } } 如果这个二叉树很庞大,反复递归函数调用开销就很大,万一堆栈溢出怎么办? 现在我们用循环改写: bool search(btree* p, int v) { while (p) { if (v == p->v) return true; else { if (v < p->v) p = p->left; else p = p->right; } } return false; } --------------------------------------------------------------------------------------------------------- 递归好处:代码更简洁清晰,可读性更好 递归可读性好这一点,对于初学者可能会反对。实际上递归的代码更清晰,但是从学习的角度要理解递归真正发生的什么,是如何调用的,调用层次和路线,调用堆栈中保存了什么,可能是不容易。但是不可否认递归的代码更简洁。一般来说,一个人可能很容易的写出前中后序的二叉树遍历的递归算法,要写出相应的非递归算法就比较考验水平了,恐怕至少一半的人搞不定。所以说递归代码更简洁明了。 递归坏处:由于递归需要系统堆栈,所以空间消耗要比非递归代码要大很多。而且,如果递归深度太大,可能系统撑不住。 楼上的有人说: 小的简单的用循环,, 太复杂了就递归吧,,免得循环看不懂 话虽然简单,其实非常有道理:对于小东西,能用循环干嘛要折腾?如果比较复杂,在系统撑的住的情况下,写递归有利于代码的维护(可读性好) 另:一般尾递归(即最后一句话进行递归)和单向递归(函数中只有一个递归调用地方)都可以用循环来避免递归,更复杂的情况则要引入栈来进行压栈出栈来改造成非递归,这个栈不一定要严格引入栈数据结构,只需要有这样的思路,用数组什么的就可以。 至于教科书上喜欢n!的示例,我想只是便于递归思路的引进和建立。真正做代码不可能的。 -------------------------------------------------------------------------------------------------------------------- 循环方法比递归方法快, 因为循环避免了一系列函数调用和返回中所涉及到的参数传递和返回值的额外开销。 递归和循环之间的选择。一般情况下, 当循环方法比较容易找到时, 你应该避免使用递归。这在问题可以按照一个递推关系式来描述时, 是时常遇到的, 比如阶乘问题就是这种情况。 反过来, 当很难建立一个循环方法时, 递归就是很好的方法。实际上, 在某些情形下, 递归方法总是显而易见的, 而循环方法却相当难找到。当某些问题的底层数据结构本身就是递归时, 则递归也就是最好的方法了。 --------------------------------------------------------------------------------------------------------------------------- 递归其实是方便了程序员难为了机器。它只要得到数学公式就能很方便的写出程序。优点就是易理解,容易编程。但递归是用栈机制实现的(c++),每深入一层,都要占去一块栈数据区域,对嵌套层数深的一些算法,递归会力不从心,空间上会以内存崩溃而告终,而且递归也带来了大量的函数调用,这也有许多额外的时间开销。所以在深度大时,它的时空性就不好了。 循环其缺点就是不容易理解,编写复杂问题时困难。优点是效率高。运行时间只因循环次数增加而增加,没什么额外开销。空间上没有什么增加。 --------------------------------------------------------------------------------------------------------------------------- 递归算法与迭代算法的设计思路区别在于:函数或算法是否具备收敛性,当且仅当一个算法存在预期的收敛效果时,采用递归算法才是可行的,否则,就不能使用递归算法。 当然,从理论上说,所有的递归函数都可以转换为迭代函数,反之亦然,然而代价通常都是比较高的。 但从算法结构来说,递归声明的结构并不总能够转换为迭代结构,原因在于结构的引申本身属于递归的概念,用迭代的方法在设计初期根本无法实现,这就像动多态的东西并不总是可以用静多态的方法实现一样。这也是为什么在结构设计时,通常采用递归的方式而不是采用迭代的方式的原因,一个极典型的例子类似于链表,使用递归定义及其简单,但对于内存定义(数组方式)其定义及调用处理说明就变得很晦涩,尤其是在遇到环链、图、网格等问题时,使用迭代方式从描述到实现上都变得很不现实

1,利用递归实现阶乘和斐波那契;

  斐波那契数列又称兔子数列:兔子数列最大的特点就是前两项之和等于后一项,比如1+1=2、1+2=3、2+3=5、3+5=8、5+8=13…

  我们用an表示一个数列的第n项,那么斐波那契数列的规律就是:an+2=an+an+1

  以下是js代码:

/*
     递归:
         函数自己调用自己 停不下来
         需要有 跳出的条件
*/
    /*
        使用递归 求n的阶乘
        假设 写好了一个函数 fn
        1:1
        2:1*2  ->fn(1)*2
        3:1*2*3 ->fn(2)*3
        4:1*2*3*4 ->fn(3)*4
        ...
        n:1*2*3...*n ->fn(n-1)*n
    */
    function fn2(n) {
        // 条件是
        if(n==1){
            return 1;
        }
        return fn2(n - 1) * n;
    }
    var result2 = fn2(4);
    console.log(result2);
/*
  实际上可以这么理解:result2 获取的只是第一个return即 4*fn2(3),即:result2 =
4*fn2(3)
    而fn2(3) = 3*fn2(2) 即:result2 =4*fn2(3) = 4*3*fn2(2) ... 以此类推,到最后 result2 =4*fn2(3) = 4*3*fn2(2) = ... =4!
*/
 
    /*
        斐波那契数列
        封装好了 函数fn
        十分耗费性能  因为函数成 指数倍 增长 每一个新的函数 都需要耗费 系统的资源
        每一次化简 都需要化到 最底部 1,2 才能够求和
        1:1 fn(1) ->1
        2:1 fn(2) ->1
        3:2 fn(2)+fn(1)
        4:3 fn(3)+fn(2)
        5:5
        6:8
        ....
        n: fn(n-1)+fn(n-2)
    */
    function fn4(n) {
        if (n == 1 || n == 2) {
            return 1;
        }
        return fn4(n - 1) + fn4(n - 2);
    }
    var result = fn4(20);
    console.log(result);
原文地址:https://www.cnblogs.com/wangtong111/p/11234826.html