逐步學習什麼是遞歸?經過使用場景來深刻認識遞歸。

遞歸算法

咱們先來看一下定義。遞歸算法,是將問題轉化爲規模縮小的同類問題的子問題,每個子問題都用一個一樣的算法去解決。通常來講,一個遞歸算法就是函數調用自身去解決它的子問題。前端

遞歸算法的特色:

在函數過程當中調用自身。 在遞歸過程當中,必須有一個明確的條件判斷遞歸的結束,既遞歸出口。 遞歸算法簡潔但效率低,一般不做爲推薦算法。算法

上面這些是百度百科的解釋,講的也是十分明確,你們配合實例來細細琢磨。數組


求一個數的n次方(n>=0)

咱們拿到問題的時候,咱們按照定義的說明,能夠先將規模縮小到同類的子問題。好比:求一個數的n次方,就是把n個相同的數相乘的積。計算過程就是第一個數乘以第二個數,再把這兩個數的乘積跟第三個數相乘,依次把上一次的乘積跟下一個數相乘,直到乘到第n個數。規律就是把這一次的乘積和下一個數相乘。遞歸的目的就是把一個大問題拆分紅若干個用一樣邏輯解決的小問題。這裏咱們用遞歸的解法就是:瀏覽器

//非尾遞歸
function com2 (num, n) {
    if(n<=0){
        return 0;
    }else if(n==1){
        return num;
    }
    return num*arguments.callee(num, n-1)
}
// 用法:
// com2(12,2)
// > 輸出144
複製代碼

咱們能夠看到函數com2第一個參數是num(數值),第二個參數是n(數值的n次方)。函數體內部判斷若是n<=0,則返回0;若是n==1則返回數值num;這裏這兩個條件就是遞歸函數的遞歸出口。n<=0是爲了處理求一個數的<=0次方時的異常處理。n==1是爲了退出遞歸循環。
函數

最後面的return num*arguments.callee(num, n-1)就是咱們的主遞歸邏輯了。把一個數乘如下一個數,直到n==1時退出遞歸循環,而且把最後的乘積一層層返回出來。優化

//尾遞歸
function com2 (num, n) {
    if(n<=0){
        return arguments[2] || 0;
    }
    var product = num;
    if(arguments[2]){
        product = num*arguments[2];
    }
    return arguments.callee(num, n-1, product)
}
// 用法:
// com2(12,2)
// > 輸出144
複製代碼

尾遞歸。爲何我要說尾遞歸,上面咱們也說了,遞歸算法簡潔但效率低,那麼有沒有優化方案呢?答案是「有」。那就是尾遞歸。ui

什麼是尾遞歸?
尾遞歸的判斷標準是函數運行【最後一步】是否只調用自身。一般會把上一個方法的返回值看成參數傳給下一個遞歸方法。
如上面代碼:return num*arguments.callee(num, n-1)就不是尾遞歸,由於最後執行的是num*函數自身調用,而不是純粹的調用函數自身。return arguments.callee(num, n-1, product)就是純粹的調用函數自身的尾遞歸。spa

那麼爲何遞歸效率低呢?
code

衆所周知,遞歸很是消耗內存,由於須要同時保存不少的調用幀,這樣,就很容易發生「棧溢出」。遞歸

尾遞歸的做用就是保留一個調用記錄。這樣速度就能夠提高上來了。

遺憾的是,當前提供尾遞歸優化的瀏覽器比較少。


實際應用場景

將數組obj格式:

var obj = [
    {id:1, parent: null},
    {id:2, parent: 1},
    {id:3, parent: 2},
];
複製代碼

轉換爲obj2格式:

var obj2 = {
    obj: {
        id: 1,
        parent: null,
        child: {
            id: 2,
            parent: 1,
            child: {
                id: 3,
                parent: 2
            }
        }
    }
}
複製代碼
代碼實現:
var obj2 = {};
function createObj2(obj, child){
    if(child.parent){
        if(obj.obj){
            createObj2(obj.obj, child);
        }else{
            if(obj.id === child.parent){
                obj.child = {
                    id: child.id,
                    parent: child.parent,
                }
            }else{
                if(obj.child){
                    createObj2(obj.child, child);
                }else{
                    console.log('obj2未匹配到對應的parent對應關係')
                }
            }
        }
    }else{
        obj.obj = {
            id: child.id,
            parent: child.parent,
            child: {}
        }
    }
}
obj.forEach((item, item_i) => {
    createObj2(obj2, item)
})
console.log('obj2:', obj2)
複製代碼

結語

通常樹狀結構的均可以使用遞歸查詢,好比:
查詢地區,樹狀的菜單等等。

注意:遞歸比普通的算法耗內存,深度遞歸的函數可能會由於返回堆棧溢出而運行失敗。在適當的時候用遞歸,不要濫用遞歸。

備註:喜歡這篇文章的話,能夠關注我,我會持續更新技術文章到個人掘金主頁,喜歡交流技術的朋友能夠加個人專業前端QQ交流羣。QQ羣號:583575104 或 366420656
相關文章
相關標籤/搜索