斐波那契數(shù)列的一些思考與延伸

先看代碼:

function fib(n){
    if(n===1){
        return 1;
    }
    if(n===2){
        return 1;
    }
    if(n>2){
        return fib(n-1)+fib(n-2);
    }
}
console.log(fib(11));

這是一個(gè)很典型的利用遞歸計(jì)算斐波那契數(shù)列。

遞歸的缺點(diǎn)也是顯而易見(jiàn)的,我們計(jì)算fib(6)時(shí) 要計(jì)算fib(5)和fib(4)

而后計(jì)算fib(7)時(shí),又要重復(fù)計(jì)算fib(6)與fib(5)

很明顯,我們之前已經(jīng)計(jì)算過(guò)了fib(6)與fib(5),現(xiàn)在重復(fù)計(jì)算,造成了浪費(fèi).

首先我們來(lái)觀察一下 當(dāng)n從20到21時(shí),調(diào)用此函數(shù) 內(nèi)部會(huì)多調(diào)用多少次此函數(shù)?

var count=0;//計(jì)數(shù)器
function fib(n){
    count++;
    if(n===1){
        return 1;
    }
    if(n===2){
        return 1;
    }
    if(n>2){
        return fib(n-1)+fib(n-2);
    }
}
console.log(fib(20));
console.log(count);//13529次
count = 0;
console.log(fib(21));
console.log(count);//21891次  從20到21 調(diào)用次數(shù)增加不少

其實(shí)我們完全可以將之前計(jì)算過(guò)的數(shù)值用一個(gè)數(shù)組保存起來(lái),如果需要重復(fù)計(jì)算,先去數(shù)組內(nèi)部查找,如果數(shù)組里面存在該結(jié)果,直接return

var cache = [0,1,1];
function fib(n){
    if(n<=2){
        return cache[n];
    }else{
        if(cache[n]){ //如果緩存數(shù)組中存在 直接返回
            return cache[n];
        }else{
            var temp = fib(n-1)+fib(n-2); //如果緩存數(shù)組中不存在 進(jìn)行遞歸
            cache[n]=temp;   //將遞歸結(jié)果存入緩存數(shù)組
            return temp;
        }
    }
 }

這樣已經(jīng)能夠節(jié)省很多遞歸造成的空間浪費(fèi)了。

但是緩存數(shù)組孤零零的放在全局作用域,不夠安全,封裝性也不好,比較寂寞。

我們希望他們聯(lián)系的能夠更緊密一些,就像一個(gè)整體。

于是有了下面的代碼:

var fib = (function(){
    var cache = [0,1,1];
    var fib = function() {
        if(n<=2){
            return cache[n];
        }else{
            if(cache[n]){ //如果緩存數(shù)組中存在 直接返回
                return cache[n];
            }else{
                var temp = fib(n-1)+fib(n-2); //如果緩存數(shù)組中不存在 進(jìn)行遞歸
                cache[n]=temp;   //將遞歸結(jié)果存入緩存數(shù)組
                return temp;
            }
        }
    }
    return fib;
})()

這樣,我們通過(guò)閉包,只能通過(guò)返回的fib方法對(duì)cache進(jìn)行操作了。

當(dāng)然,你也可以像下面這樣寫(xiě):

function createCache(){
    var cache=[0,1,1];//緩存數(shù)組被封裝在閉包中,外界只能通過(guò)返回的方法進(jìn)行操作
    return function editCache(index,value){
        if(value==undefined){
            return cache[index];
        }else{
            cache[index]=value;
        }
    }
 }

 var fibCache=createCache();//創(chuàng)建緩存數(shù)組并且獲取接口方法

 function fib(n){
    if(n<=2){
        return fibCache(n);
    }else{
        if(fibCache(n)){
            return fibCache(n);
        }else{
            var temp = fib(n-1)+fib(n-2);
            fibCache(n,temp);
            return temp;
        }
    }
 }

遞歸效率低是函數(shù)調(diào)用的開(kāi)銷導(dǎo)致的。

在一個(gè)函數(shù)調(diào)用之前需要做許多工作,比如準(zhǔn)備函數(shù)內(nèi)局部變量使用的空間、搞定函數(shù)的參數(shù)等等,這些事情每次調(diào)用函數(shù)都需要做,因此會(huì)產(chǎn)生額外開(kāi)銷導(dǎo)致遞歸效率偏低 來(lái)自知乎

其實(shí)一般遞歸的方法我們都可以通過(guò)迭代的方式來(lái)做,for循環(huán)就是一個(gè)很好的選擇:

function fib(n) {
    if (n == 1 || n == 2) {
        return 1;
    }
    var arr = [0,1,1];
    for(var i = 2; i <= n; i++) {
        arr[i] = arr[i-1] + arr[i-2];
    }
    return arr[n];
}

看起來(lái)也挺好的,是吧。

下面進(jìn)入算法時(shí)間,題目來(lái)自《劍指Offer》,當(dāng)然,遞歸依然是主角。

題目一:
一只青蛙一次可以跳上 1 級(jí)臺(tái)階,也可以跳上 2 級(jí)。求該青蛙跳上一個(gè) n 級(jí)的臺(tái)階總共有多少種跳法?

解題:數(shù)學(xué)歸納法。
1級(jí)臺(tái)階,1種跳法,直接跳上1級(jí)臺(tái)階 f(1) = 1;

2級(jí)臺(tái)階,2種跳法,直接跳上2級(jí)臺(tái)階或者連續(xù)跳兩次,每次一級(jí) f(2) = 2;

3級(jí)臺(tái)階,如果第一次跳1級(jí),剩下2級(jí)臺(tái)階,f(2) = 2種跳法;如果第一次跳2級(jí),剩下1級(jí)臺(tái)階,f(1) = 1種跳法。f(3) = f(2) + f(1) = 2 + 1 = 3;

4級(jí)臺(tái)階,如果第一次跳1級(jí),剩下3級(jí)臺(tái)階,由上一條可知有3種跳法;如果第一次跳2級(jí),剩下2級(jí)臺(tái)階,由上上條可知有2種跳法,f(4) = f(3) + f(2) = 3 + 2 = 5;

n級(jí)臺(tái)階,f(n) = f(n-1) + f(n-2)

很熟悉對(duì)嗎?

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

題目二:
一只青蛙一次可以跳上 1 級(jí)臺(tái)階,也可以跳上 2 級(jí)……它也可以跳上 n 級(jí)。求該青蛙跳上一個(gè) n 級(jí)的臺(tái)階總共有多少種跳法?

解題:繼續(xù)歸納。
1級(jí)臺(tái)階,1種跳法

2級(jí)臺(tái)階,2種跳法

3級(jí)臺(tái)階,4種跳法 f(3) = f(2) + f(1) + 1 = 4

4級(jí)臺(tái)階,第一次跳1級(jí),后面有f(3)種跳法,第一次跳2級(jí),后面有f(2)種跳法,第一次跳3級(jí),后面有f(1)種跳法,第一次跳4級(jí),沒(méi)了
總共f(4) = f(3) + f(2) + f(1) + 1 = 8種跳法

5級(jí)臺(tái)階,f(5) = f(4) + f(3) + f(2) + f(1) + 1 = 16種跳法

歸納得,f(n) = f(n-1) + f(n-2) + ... + f(1) + 1

function jump(n) {
    if (n <= 0) {
        return;
    } else if (n > 0 && n <= 2) {
        return n;
    } else if (n > 2) {
        var tmp = 0;
        while(number > 1){
            tmp+=jump(n-1);
            number--;
        }
        return tmp+1;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文鏈接:http://blog.csdn.net/qq_22329521/article/details/529...
    越長(zhǎng)越圓閱讀 1,682評(píng)論 0 1
  • 斐波那契數(shù)列(Fibonacci sequence),又稱黃金分割數(shù)列。 解法: 1、遞歸 2、累加(去重復(fù)) 3...
    Arya鑫閱讀 641評(píng)論 0 0
  • 零碎 標(biāo)識(shí)符 關(guān)鍵字 運(yùn)算符號(hào) 邏輯運(yùn)算符 執(zhí)行順序& if else & switch 順序執(zhí)行 分支執(zhí)行 循環(huán)...
    陳小飄閱讀 421評(píng)論 0 0
  • 題目描述 大家都知道斐波那契數(shù)列,現(xiàn)在要求輸入一個(gè)整數(shù)n,請(qǐng)你輸出斐波那契數(shù)列的第n項(xiàng)。n<=39 常見(jiàn)的遞歸做法...
    clshinem閱讀 272評(píng)論 1 1
  • 我辟谷初心是想排腸毒減腰圍,辟前體重51公斤。半個(gè)月前,我和老公說(shuō)我想辟谷。他立馬長(zhǎng)篇大論專家式的發(fā)表一通...
    謝謝環(huán)閱讀 1,212評(píng)論 0 2

友情鏈接更多精彩內(nèi)容