javascript遞歸


遞歸函數就是會直接或者間接調用自身的一種函數。遞歸是一種強大的編程技術,它把一個問題分解為一組相似的子問題,調用自身去解決它的子問題。

一、漢諾塔

問題描述:有3根柱子和一套直徑各不相同的空心圓盤。開始時源柱子上的所有圓盤都按照從小到大的順序堆疊。目標是通過每次移動一個圓盤到另一根柱子,最終把一堆圓盤移動到目標柱子上,過程中不允許把較大的圓盤放置在較小的圓盤上。

var hanoi=function(disc,src,aux,dst){
    if(disc>0){
        hanoi(disc-1,src,dst,aux);
        console.log("move "+disc+" from "+src+" to "+dst);
        hanoi(disc-1,aux,src,dst);
    }
}

圓盤數量為3的時候解法為:

hanoi函數把一堆圓盤從一根柱子移動到另一根柱子,必要時使用輔助的柱子。

它把問題分解成3個子問題:
首先,移動一對圓盤中較小的圓盤到輔助柱子上,從而露出下面較大的圓盤。

然后,移動下面較大的圓盤到目標柱子上。

最后,它將剛才較小的圓盤從輔助的柱子上再移動到目標柱子上。

傳遞給hanoi函數的參數包括當前移動的圓盤編號和它將用到的3根柱子。當它調用自身的時候,它去處理當前正在處理的圓盤之上的圓盤。最終,它會以一個不存在的圓盤編號去調用。此時,不執行任何操作。由於該函數對非法值不予理會,就不用擔心它會導致死循環。

二、DOM遍歷

遞歸函數可以非常高效地操作樹形結構,比如瀏覽器文檔對象模型DOM。每次遞歸調用處理指定樹的一小段。

html結構如下:

<body>
<div class="test">測試div</div>
<span class="test">測試span</span>
<div class="test1">test1 div</div>
</body>

js如下:

<script>
/*定義walk_the_DOM函數,它從某個指定的節點開始,按HTML源碼中的順序訪問該樹的每個節點。
它會調用一個函數,並依次傳遞每個節點給它。walk_the_DOM調用自身去處理每一個子節點。
*/
var walk_the_DOM=function walk(node,func){
    func(node);
    node=node.firstChild;
    while(node){
        walk(node,func);
        node=node.nextSibling;
    }
}

/*定義getElementsByAttribute函數。它以一個屬性名稱字符串和一個可選的匹配值作為參數。
它調用walk_the_DOM,傳遞一個用來查找節點屬性名的函數作為參數。
匹配的節點會累加到一個結果數組中。
*/
var getElementsByAttribute=function(att,value){
    var results=[];

    walk_the_DOM(document.body,function(node){
        var actual=node.nodeType===1&&node.getAttribute(att);
        if(typeof actual==='string' &&( actual===value|| typeof value!=='string')){
            results.push(node);
        }
    });
    return results;
}
console.log(getElementsByAttribute("class","test"));//[div.test, span.test]

三、命名函數表達式和遞歸

1、遞歸問題

求階乘的函數:

function factorial(num){
    if(num<=1){
        return 1;
    }else{
        return num*factorial(num-1);
    }
}

正常情況運行沒問題,但是下面操作會讓它出錯:

var anotherFactorial=factorial;//把函數保存在遍歷anotherFactorial中
factorial=null;//factorial置為null,此時指向原始函數的引用只剩一個
anotherFactorial(3)

factorial已經不再是函數,所以會報錯。

2、arguments.callee實現遞歸

arguments.callee是一個指向正在執行的函數的指針,因此可以用它來實現對函數的遞歸調用

//console
function factorial(num){
    if(num<=1){
        return 1;
    }else{
        return num*arguments.callee(num-1);
    }
}
var anotherFactorial=factorial;
factorial=null;
anotherFactorial(3) //6

用arguments.callee代替函數名,可以確保無論怎樣調用函數都不會出問題。因此,在編寫遞歸函數時,使用arguments.callee總比使用函數名更保險。

問題:在嚴格模式下,不能通過腳本訪問arguments.callee,訪問這個屬性會報錯。

更多嚴格模式相關內容可參考:javascript 語句和嚴格模式(三)

3、命名函數表達式實現遞歸

 創建一個名為f()的命名函數表達式,然后賦值給factorial,即使把函數賦值給了另一個變量,函數的名字f仍然有效,所以遞歸調用照樣能正常完成。

這種方式在嚴格模式和非嚴格模式都可行。

//console
var factorial =function f(num){
'use strict'
    if(num<=1){
        return 1;
    }else{
        return num*f(num-1);
    }
}

factorial(3)
//6
var anotherFactorial=factorial;
factorial=null;
anotherFactorial(3)
//6

命名函數表達式也有它的一些經典bug,見javascript 函數和作用域(函數,this)(六)

 

 

本文作者starof,因知識本身在變化,作者也在不斷學習成長,文章內容也不定時更新,為避免誤導讀者,方便追根溯源,請諸位轉載注明出處:http://www.cnblogs.com/starof/p/6510723.html有問題歡迎與我討論,共同進步。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM