1. 程式人生 > 實用技巧 >第九章 遞迴

第九章 遞迴

自我測試

本篇文章的測試用例及除錯方法見前言

說明

遞迴是一種解決問題的方法,他從解決問題的各個小部分開始,直到解決最初的大問題.遞迴通常涉及函式呼叫自身.

const test = (i) => {
    console.log(i)
    test(++i);  
}

這是一種明顯的遞迴,自己呼叫自己

function test2(i){
    console.log("test2: " + i++)
    test(i)
}

function test(i){
    console.log("test: " + i++)
    test2(i)
}  

這個也是一種遞迴,通過兩個方法之間的相互呼叫,然後實現遞迴.但是這種程式碼我們一般是不會直接拿去執行的.為什麼呢???

相信你也看出來了,上面的遞迴寫法有問題,我呼叫我自己,然後自己呼叫自己,然後自己又呼叫了自己......,形成了一個閉環,所以遞迴的最重要的一個點出現了 基線條件(跳出迴圈的條件)

呼叫棧(這個對後面分析遞迴很重要)

每當一個函式被一個演算法呼叫時,該函式會進入呼叫棧的頂部.當使用遞迴的時候,每個函式呼叫都會堆疊在呼叫棧的頂部,這是因為每個呼叫都可能依賴前一個呼叫的結果

尾部呼叫優化(ES2015新知識點)

案例

let i = 0;
function recursiveFn(){
    i++;
	recursiveFn();
}
try{
    recursiveFn();
}catch(ex){
    console.log(ex)
}

如果函式內的最後一個操作是呼叫函式(如案例),會通過"跳轉指令"(jump)而不是"子程式呼叫"(subroutine call)來控制.也就是說,在ES2015中,這裡的程式碼可以一直執行下去.因此,具有停止遞迴的基線條件非常重要尾呼叫優化更多資訊

練習

階乘

說明:

5的階乘: 5 * 4 * 3 * 2 * 1

9的階乘: 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1

程式碼

function recursionFactorial(num: number): number {
    //找到結束條件
    if (num === 1) {
        return 1;
    }
    return num * recursionFactorial(num - 1);
}

還是一定要找到 基線條件,這個是遞迴的必要條件,不然你的程式會死迴圈,然後卡死

圖解

是否還喜歡這種圖解,如果喜歡,可以進入下一篇章第十章 樹,帶你走進樹的遍歷

斐波那契數列

說明

0 1 1 2 3 5 8 13 21 34 ....

第1個數: 0

第2個數: 1

第3個數 = 第2個數 + 第1個數

第4個數 = 第2個數 + 第3個數

第5個數 = 第4個數 + 第3個數

........

圖解

程式碼

/*求n個斐波那契數的和*/
function newRecursionSequence(n: number): number {
    if (n === 0) return 0;
    if (n <= 2) return 1
    return newRecursionSequence(n - 1) + newRecursionSequence(n - 2);
}

優化版程式碼

function newNewRecursionSequence(n: number) {
    let memo: Array<number> = [0, 1];
    let fbnc = (n: number): number => {
        if (memo[n] != null) return memo[n];
        //新算出的值儲存在memo數組裡
        return memo[n] = fbnc(n - 1) + fbnc(n - 2);
    }
    return fbnc(n);
}

優化版對比第一版好在對資料進行了快取,這樣就不用反覆的計算資料(圖解中可以看到我們反覆計算了n為 3 ,2等時的值)