递归、迭代与动态规划

来源:互联网 发布:java pfx证书读取 编辑:程序博客网 时间:2024/05/29 08:55

在这篇博客里,我将以计算斐波那契数列指定位置的数为例介绍递归、迭代与动态规划。首先我们得弄清楚这三者的定义。
递归——程序调用自身,也就是函数自己调用自己。递归通常从顶部将问题分解,通过解决掉所有分解出来的小问题,来解决整个问题;
迭代——利用变量的原值推算出变量的一个新值。递归中一定有迭代,但是迭代中不一定有递归,大部分可以相互转换
动态规划——通常与递归相反,其从底部开始解决问题。将所有小问题解决掉,进而解决的整个问题。
下面我们以斐波那契数列为例演示这三种算法。
斐波那契数列的长相是酱紫的:
0,1,1,2,3,5,8,13,21,34,55
数学定义上的斐波那契数列没有最开始的0,也就是说其第一位应该是从上面的数列的第一个1开始,但为了更好的揭示斐波那契数列的规律,我们设其第0位为0,可以看出,除了第0,1位以外斐波那契数列的其他位上的数都是前两项之和。
现在用递归计算指定位置上的数

function recurFib(n){    if(n<2&&n>-1){        return n;    }else{        return recurFib(n-1)+recurFib(n-2);    }}

为了方便展示结果,三种算法我们都以下面的方式在控制台中输出结果

document.querySelector("#recursion").onclick=function(){        //获取输入框的值        var fibonacci=document.querySelector("#fibonacci").value;        var start=new Date().getTime();        console.log(recurFib(fibonacci));        var stop=new Date().getTime();        console.log("递归用时:"+(stop-start)+"毫秒");    };

可以看到,在使用递归时,recurFib函数内部存在调用自身的部分,而且我们是把recurFib(n)分解为recurFib(n-1),recurFib(n-2),也就是把大问题分解为若干个小问题。
接下来我们试着用迭代

function iterFib(n){    if(n<2&&n>-1){        return n;    }else{        var result=0,a=0,b=1;        for(var i=2;i<=n;i++){            result=a+b;            if(a<b){                a=result;            }else{                b=result;            }        }        return result;    }}

在else块中存在一个for循环,这个for循环不断通过前两项的数值来计算当前位置的数值,并且如果有可能还将新值赋予旧值。
下面我们使用动态规划

function dynFib(n){    var val=[];    if(n<2&&n>-1){        return n;    }else{        val[0]=0;        val[1]=1;        for(var i=2;i<=n;i++){            val[i]=val[i-1]+val[i-2];        }        return val[n];    }}

在使用动态规划实现时,我们用val数组保留中间值,这些中间值即是动态规划定义中的“小问题”。但需要注意的是用动态规划解决斐波那契数列时,可以不用数组(其他问题中也有可能用不到数组)。需要用到数组是因为动态规划过程中通常需要保留中间值。因为在计算某个位置上的数时只需要用到前两位的值,所以我们只需要动态的保留前两位的值即可——这样子动态规划的实现就和迭代是一样的了(但在其他问题上可能是不一样的),这也说明了动态规划可以用迭代来实现。
在实际编程中,能用迭代或者动态规划的不用递归,递归调用函数,浪费空间,并且递归太深容易造成堆栈的溢出。在结果输出到控制台中时,我们可以发现对于同一个n,递归的耗时比动态规划和迭代长,而且n越大这种差异越明显。运算耗时因电脑配置而异,我的电脑上递归超过35就很吃力了,有时候还会卡死。

原创粉丝点击