在多年的編程中,我已經使用了不少遞歸來解決簡單的問題,可是我徹底意識到有時因爲內存/速度問題須要迭代。 node
所以,在好久之前的某個時候,我去嘗試查找是否存在將通用遞歸方法轉換爲迭代的任何「模式」或教科書方法,卻一無所得。 或至少沒有什麼我能記住的會有所幫助。 編程
遞歸不過是從另外一個函數調用另外一個函數的過程,只有此過程是經過單獨調用一個函數來完成的。 衆所周知,當一個函數調用另外一個函數時,第一個函數保存其狀態(其變量),而後將控件傳遞給被調用的函數。 能夠經過使用相同的變量名來調用被調用函數,例如fun1(a)能夠調用fun2(a)。 當咱們進行遞歸調用時,沒有新的事情發生。 一個函數經過傳遞相同的類型和類似的名稱變量來調用自身(可是很明顯,存儲在變量中的值是不一樣的,只是名稱保持不變。)。 可是,在每次調用以前,函數都會保存其狀態,而且保存過程將繼續。 保存在堆棧上。 函數
如今,堆棧開始播放了。 spa
所以,若是您編寫一個迭代程序並每次將狀態保存在堆棧上,而後在須要時從堆棧中彈出值,那麼您已成功將遞歸程序轉換爲迭代程序! rest
證實是簡單和分析的。 code
在遞歸中,計算機維護堆棧,而在迭代版本中,您將必須手動維護堆棧。 遞歸
考慮一下,只需將深度優先搜索(在圖形上)遞歸程序轉換爲dfs迭代程序。 內存
祝一切順利! 開發
一般,將堆棧溢出用於遞歸函數的技術稱爲蹦牀技術,這是Java開發人員普遍採用的技術。 get
可是,對於C#,這裏有一個小的輔助方法,能夠將遞歸函數變成迭代的,而無需更改邏輯或使代碼難以理解。 C#是一種很棒的語言,它可能帶來使人驚奇的東西。
它經過使用輔助方法包裝方法的某些部分來工做。 例如如下遞歸函數:
int Sum(int index, int[] array) { //This is the termination condition if (int >= array.Length) //This is the returning value when termination condition is true return 0; //This is the recursive call var sumofrest = Sum(index+1, array); //This is the work to do with the current item and the //result of recursive call return array[index]+sumofrest; }
變成:
int Sum(int[] ar) { return RecursionHelper<int>.CreateSingular(i => i >= ar.Length, i => 0) .RecursiveCall((i, rv) => i + 1) .Do((i, rv) => ar[i] + rv) .Execute(0); }
努力進行遞歸調用Tail Recursion (遞歸,其中最後一條語句是遞歸調用)。 一旦有了這些,將其轉換爲迭代一般很是容易。
在Google上搜索「繼續傳遞樣式」。 有一個轉換爲尾部遞歸樣式的通用過程。 還有將尾遞歸函數轉換爲循環的通用過程。
要尋找的一種模式是在函數末尾進行遞歸調用(所謂的尾遞歸)。 能夠輕鬆地將其替換一下子。 例如,函數foo:
void foo(Node* node) { if(node == NULL) return; // Do something with node... foo(node->left); foo(node->right); }
以對foo的調用結束。 能夠替換爲:
void foo(Node* node) { while(node != NULL) { // Do something with node... foo(node->left); node = node->right; } }
這樣就消除了第二個遞歸調用。