遞歸調用裏的性能問題(js)

說明

這是在codewars.com上刷的一道js練習題,在此作個記錄算法

問題描述

The Fibonacci sequence is traditionally used to explain tree recursion.api

斐波那契序列一般是用來解釋遞歸調用。數組

function fibonacci(n) {
    if(n==0 || n == 1)
        return n;
    return fibonacci(n-1) + fibonacci(n-2);
}

This algorithm serves welll its educative purpose but it's tremendously inefficient, not only because of recursion, but because we invoke the fibonacci function twice, and the right branch of recursion (i.e. fibonacci(n-2)) recalculates all the Fibonacci numbers already calculated by the left branch (i.e. fibonacci(n-1)).緩存

這個算法以教學爲目的,但很是低效的,不只由於遞歸,並且兩次調用fibonacci函數,在函數裏面右側調用的fibonacci(n-2) 在表達式左側調用fibonacci(n-1)時就已徹底計算過一遍。數據結構

This algorithm is so inefficient that the time to calculate any Fibonacci number over 50 is simply too much. You may go for a cup of coffee or go take a nap while you wait for the answer. But if you try it here in Code Wars you will most likely get a code timeout before any answers.函數

這個算法效率是如此之低,斐波納契數超過50的實在太多了。你能夠去喝杯咖啡或去睡午覺時等待答案。但若是你就用這個代碼在codewars上極可能獲得一個超時錯誤。性能

For this particular Kata we want to implement the memoization solution. This will be cool because it will let us keep using the tree recursion algorithm while still keeping it sufficiently optimized to get an answer very rapidly.測試

對於這個特定卡塔(相似打怪升級裏面的級數),咱們想實現緩存的解決方案。這是特別酷的,由於它將讓咱們繼續使用遞歸算法,同時仍然保持足夠迅速的獲得一個答案。this

The trick of the memoized version is that we will keep a cache data structure (most likely an associative array) where we will store the Fibonacci numbers as we calculate them. When a Fibonacci number is calculated, we first look it up in the cache, if it's not there, we calculate it and put it in the cache, otherwise we returned the cached number.code

memoize的版本的訣竅是,保持一個緩存數據結構(最有可能的關聯數組),將斐波納契數列的值緩存。當獲取一個斐波那契數列值時,首先在緩存中查找,若是有則直接返回值,若是沒有,再計算並把它放進緩存。

Refactor the function into a recursive Fibonacci function that using a memoized data structure avoids the deficiencies of tree recursion Can you make it so the memoization cache is private to this function?

使用memoize的數據結構重構函數的遞歸Fibonacci以免遞歸調用的缺陷。

分析

斐波那契數列裏面不斷的遞歸調用自身,列入輸入的是 70,那麼須要計算69和68的值。
在計算69的過程當中又計算了 6八、6七、、、、、1。 計算 68的過程又計算了 6七、6六、、、、、、、1的值,如此重複計算的值太多了,花費的時間也就比較多。

緩存思想剛好能夠減小沒必要要的重複計算。當第一遍計算69的值時就遞歸計算了 6八、6七、6六、、、1的值,以後的每次都先查看是否有緩存,有就直接返回緩存值,避免了重複計算。

代碼

let cache = {};
let fibonacci = function(n) {
    if(n==0 || n == 1)
        return n;
    if(cache[n]){
      return cache[n];
    }
    
    return cache[n] = fibonacci(n-1) + fibonacci(n-2);
}

性能測試

//沒有緩存時
let tesetNum = 40;
console.time('NoCache');
function fibonacci1(n) {
    if(n==0 || n == 1)
        return n;
    return fibonacci1(n-1) + fibonacci1(n-2);
}
fibonacci1(tesetNum);
console.timeEnd('NoCache');

// 使用緩存時
console.time("HasCache");
let cache = {};
let fibonacci = function(n) {
    if(n==0 || n == 1)
        return n;
    if(cache[n]){
      return cache[n];
    }
    
    return cache[n] = fibonacci(n-1) + fibonacci(n-2);
}
fibonacci(tesetNum);
console.timeEnd('HasCache');

// 輸出
// NoCache: 1717.834ms
// HasCache: 0.159ms

經過性能測試能夠看到,當測試數是40時不適用緩存消耗的時間就是使用緩存的1700多倍(好可怕的數據),我試了下當測試數據是300時,,,,,,,,我就等不急它的執行了。

使用場景

當遞歸調用裏有大量重複計算的情景,或者組件、數據等重複加載的狀況下,使用緩存是個不錯的選擇(典型的以空間換時間)

相關文章
相關標籤/搜索