如何利用循環代替遞歸以防止棧溢出(譯)

摘要:咱們常常會用到遞歸函數,可是若是遞歸深度太大時,每每致使棧溢出。而遞歸深度每每不太容易把握,因此比較安全一點的作法就是:用循環代替遞歸。文章最後的原文裏面講了如何用10步實現這個過程,至關精彩。本文翻譯了這篇文章,並加了本身的一點註釋和理解。php

 

目錄 c++

  1.  簡介
  2. 模擬函數的目的
  3. 遞歸和模擬函數的優缺點
  4. 用棧和循環代替遞歸的10個步驟
  5. 替代過程的幾個簡單例子
  6. 更多的例子
  7. 結論
  8. 參考
  9. 協議

1 簡介git

  通常咱們在進行排序(好比歸併排序)或者樹操做時會用到遞歸函數。可是若是遞歸深度達到必定程度之後,就會出現意想不到的結果好比堆棧溢出。雖然有不少有經驗的開發者都知道了如何用循環函數或者棧加while循環來代替遞歸函數,以防止棧溢出,但我仍是想分享一下這些方法,這或許會對初學者有很大的幫助。github

2 模擬函數的目的算法

  若是你正在使用遞歸函數,而且沒有控制遞歸調用,而棧資源又比較有限,調用層次過深的時候就可能致使棧溢出/堆衝突模擬函數的目的就是在堆中開闢區域來模擬棧的行爲,這樣你就能控制內存分配和流處理,從而避免棧溢出。若是能用循環函數來代替效果會更好,這是一個比較須要時間和經驗來處理的事情,出於這些緣由,這篇文章爲初學者提供了一個簡單的參考,怎樣使用循環函數來替代遞歸函數,以防止棧溢出?安全

3 遞歸函數和模擬函數的優缺點ide

  遞歸函數:函數

  優勢:算法比較直觀。能夠參考文章後面的例子oop

  缺點:可能致使棧溢出或者堆衝突性能

  你能夠試試執行下面兩個函數(後面的一個例子),IsEvenNumber(遞歸實現)IsEvenNumber(模擬實現),他們在頭文件"MutualRecursion.h"中聲明。你能夠將傳入參數設定爲10000,像下面這樣:

#include "MutualRecursion.h" 

bool result = IsEvenNumberLoop(10000); // 成功返回

bool result2 = IsEvenNumber(10000);     // 會發生堆棧溢出

 有些人可能會問:若是我增長棧的容量不就能夠避免棧溢出嗎?好吧,這只是暫時的解決問題的辦法,若是調用層次愈來愈深,頗有可能會再次發生溢出。

   模擬函數:

  優勢:能避免棧溢出或者堆衝突錯誤,能對過程和內存進行更好的控制

  缺點:算法不是太直觀,代碼難以維護

 

4 用棧和循環代替遞歸的10個步驟

第一步

定義一個新的結構體Snapshot,用於保存遞歸結構中的一些數據和狀態信息

Snapshot內部須要包含的變量有如下幾種:

  A 通常當遞歸函數調用自身時,函數參數會發生變化。因此你須要包含變化的參數,引用除外好比下面的例子中,參數n應該包含在結構體中,而retVal不須要。

void SomeFunc(int n, int &retVal);

  B 階段性變量"stage"(一般是一個用來轉換到另外一個處理分支的整形變量),詳見第六條規則

  C 函數調用返回之後還須要繼續使用的局部變量(通常在二分遞歸和嵌套遞歸中很常見)

代碼:

 1 // Recursive Function "First rule" example
 2 int SomeFunc(int n, int &retIdx)
 3 {
 4    ...
 5    if(n>0)
 6    {
 7       int test = SomeFunc(n-1, retIdx);
 8       test--;
 9       ...
10       return test;
11    }
12    ...
13    return 0;
14 }
15 
16 
17 // Conversion to Iterative Function
18 int SomeFuncLoop(int n, int &retIdx)
19 {
20     // (First rule)
21     struct SnapShotStruct {
22        int n;        // - parameter input
23        int test;     // - local variable that will be used 
24                      //     after returning from the function call
25                      // - retIdx can be ignored since it is a reference.
26        int stage;    // - Since there is process needed to be done 
27                      //     after recursive call. (Sixth rule)
28     };
29     ...
30 }
View Code

 第二

在函數的開頭建立一個局部變量,這個值扮演了遞歸函數的返回函數角色。它至關於爲每次遞歸調用保存一個臨時值,由於C++函數只能有一種返回類型,若是遞歸函數的返回類型是void,你能夠忽略這個局部變量。若是有缺省的返回值,就應該用缺省值初始化這個局部變量。

 1 // Recursive Function "Second rule" example
 2 int SomeFunc(int n, int &retIdx)
 3 {
 4    ...
 5    if(n>0)
 6    {
 7       int test = SomeFunc(n-1, retIdx);
 8       test--;
 9       ...
10       return test;
11    }
12    ...
13    return 0;
14 }
15 
16 // Conversion to Iterative Function
17 int SomeFuncLoop(int n, int &retIdx)
18 {
19      // (First rule)
20     struct SnapShotStruct {
21        int n;        // - parameter input
22        int test;     // - local variable that will be used 
23                      //     after returning from the function call
24                      // - retIdx can be ignored since it is a reference.
25        int stage;    // - Since there is process needed to be done 
26                      //     after recursive call. (Sixth rule)
27     };
28 
29     // (Second rule)
30     int retVal = 0;  // initialize with default returning value
31 
32     ...
33     // (Second rule)
34     return retVal;
35 }
View Code

 第三

建立一個棧用於保存「Snapshot」結構體類型變量

 1 // Recursive Function "Third rule" example
 2 
 3 // Conversion to Iterative Function
 4 int SomeFuncLoop(int n, int &retIdx)
 5 {
 6      // (First rule)
 7     struct SnapShotStruct {
 8        int n;        // - parameter input
 9        int test;     // - local variable that will be used 
10                      //     after returning from the function call
11                      // - retIdx can be ignored since it is a reference.
12        int stage;    // - Since there is process needed to be done 
13                      //     after recursive call. (Sixth rule)
14     };
15 
16     // (Second rule)
17     int retVal = 0;  // initialize with default returning value
18 
19     // (Third rule)
20     stack<SnapShotStruct> snapshotStack;
21     ...
22     // (Second rule)
23     return retVal;
24 }
View Code

 第四

建立一個新的」Snapshot」實例,而後將其中的參數等初始化,並將「Snapshot」實例壓入棧

 1 // Recursive Function "Fourth rule" example
 2 
 3 // Conversion to Iterative Function
 4 int SomeFuncLoop(int n, int &retIdx)
 5 {
 6      // (First rule)
 7     struct SnapShotStruct {
 8        int n;        // - parameter input
 9        int test;     // - local variable that will be used 
10                      //     after returning from the function call
11                      // - retIdx can be ignored since it is a reference.
12        int stage;    // - Since there is process needed to be done 
13                      //     after recursive call. (Sixth rule)
14     };
15 
16     // (Second rule)
17     int retVal = 0;  // initialize with default returning value
18 
19     // (Third rule)
20     stack<SnapShotStruct> snapshotStack;
21 
22     // (Fourth rule)
23     SnapShotStruct currentSnapshot;
24     currentSnapshot.n= n;          // set the value as parameter value
25     currentSnapshot.test=0;        // set the value as default value
26     currentSnapshot.stage=0;       // set the value as initial stage
27 
28     snapshotStack.push(currentSnapshot);
29 
30     ...
31     // (Second rule)
32     return retVal;
33 }
View Code

 第五

寫一個while循環,使其不斷執行直到棧爲空。while循環的每一次迭代過程當中,彈出」Snapshot「對象。

 1 // Recursive Function "Fifth rule" example
 2 
 3 // Conversion to Iterative Function
 4 int SomeFuncLoop(int n, int &retIdx)
 5 {
 6      // (First rule)
 7     struct SnapShotStruct {
 8        int n;        // - parameter input
 9        int test;     // - local variable that will be used 
10                      //     after returning from the function call
11                      // - retIdx can be ignored since it is a reference.
12        int stage;    // - Since there is process needed to be done 
13                      //     after recursive call. (Sixth rule)
14     };
15     // (Second rule)
16     int retVal = 0;  // initialize with default returning value
17     // (Third rule)
18     stack<SnapShotStruct> snapshotStack;
19     // (Fourth rule)
20     SnapShotStruct currentSnapshot;
21     currentSnapshot.n= n;          // set the value as parameter value
22     currentSnapshot.test=0;        // set the value as default value
23     currentSnapshot.stage=0;       // set the value as initial stage
24     snapshotStack.push(currentSnapshot);
25     // (Fifth rule)
26     while(!snapshotStack.empty())
27     {
28        currentSnapshot=snapshotStack.top();
29        snapshotStack.pop();
30        ...
31     }
32     // (Second rule)
33     return retVal;
34 }
View Code

 第六

  1. 將當前階段一分爲二(針對當前只有單一遞歸調用的情形)。第一個階段表明了下一次遞歸調用以前的狀況,第二階段表明了下一次遞歸調用完成並返回以後的狀況(返回值已經被保存,並在此以前被累加)
  2. 若是當前階段有兩次遞歸調用,就必須分爲3個階段。階段1:第一次調用返回以前,階段2:階段1執行的調用過程。階段3:第二次調用返回以前。
  3. 若是當前階段有三次遞歸調用,就必須至少分爲4個階段。
  4. 依次類推。
 1 // Recursive Function "Sixth rule" example
 2 int SomeFunc(int n, int &retIdx)
 3 {
 4    ...
 5    if(n>0)
 6    {
 7       int test = SomeFunc(n-1, retIdx);
 8       test--;
 9       ...
10       return test;
11    }
12    ...
13    return 0;
14 }
15 
16 // Conversion to Iterative Function
17 int SomeFuncLoop(int n, int &retIdx)
18 {
19      // (First rule)
20     struct SnapShotStruct {
21        int n;        // - parameter input
22        int test;     // - local variable that will be used 
23                      //     after returning from the function call
24                      // - retIdx can be ignored since it is a reference.
25        int stage;    // - Since there is process needed to be done 
26                      //     after recursive call. (Sixth rule)
27     };
28     // (Second rule)
29     int retVal = 0;  // initialize with default returning value
30     // (Third rule)
31     stack<SnapShotStruct> snapshotStack;
32     // (Fourth rule)
33     SnapShotStruct currentSnapshot;
34     currentSnapshot.n= n;          // set the value as parameter value
35     currentSnapshot.test=0;        // set the value as default value
36     currentSnapshot.stage=0;       // set the value as initial stage
37     snapshotStack.push(currentSnapshot);
38     // (Fifth rule)
39     while(!snapshotStack.empty())
40     {
41        currentSnapshot=snapshotStack.top();
42        snapshotStack.pop();
43        // (Sixth rule)
44        switch( currentSnapshot.stage)
45        {
46        case 0:
47           ...      // before ( SomeFunc(n-1, retIdx); )
48           break; 
49        case 1: 
50           ...      // after ( SomeFunc(n-1, retIdx); )
51           break;
52        }
53     }
54     // (Second rule)
55     return retVal;
56 }
View Code

 第七

根據階段變量stage的值切換到相應的處理流程並處理相關過程。

 1 // Recursive Function "Seventh rule" example
 2 int SomeFunc(int n, int &retIdx)
 3 {
 4    ...
 5    if(n>0)
 6    {
 7       int test = SomeFunc(n-1, retIdx);
 8       test--;
 9       ...
10       return test;
11    }
12    ...
13    return 0;
14 }
15 
16 // Conversion to Iterative Function
17 int SomeFuncLoop(int n, int &retIdx)
18 {
19      // (First rule)
20     struct SnapShotStruct {
21        int n;        // - parameter input
22        int test;     // - local variable that will be used 
23                      //     after returning from the function call
24                      // - retIdx can be ignored since it is a reference.
25        int stage;    // - Since there is process needed to be done 
26                      //     after recursive call. (Sixth rule)
27     };
28 
29     // (Second rule)
30     int retVal = 0;  // initialize with default returning value
31 
32     // (Third rule)
33     stack<SnapShotStruct> snapshotStack;
34 
35     // (Fourth rule)
36     SnapShotStruct currentSnapshot;
37     currentSnapshot.n= n;          // set the value as parameter value
38     currentSnapshot.test=0;        // set the value as default value
39     currentSnapshot.stage=0;       // set the value as initial stage
40 
41     snapshotStack.push(currentSnapshot);
42 
43     // (Fifth rule)
44     while(!snapshotStack.empty())
45     {
46        currentSnapshot=snapshotStack.top();
47        snapshotStack.pop();
48 
49        // (Sixth rule)
50        switch( currentSnapshot.stage)
51        {
52        case 0:
53           // (Seventh rule)
54           if( currentSnapshot.n>0 )
55           {
56              ...
57           }
58           ...
59           break; 
60        case 1: 
61           // (Seventh rule)
62           currentSnapshot.test = retVal;
63           currentSnapshot.test--;
64           ...
65           break;
66        }
67     }
68     // (Second rule)
69     return retVal;
70 }
View Code

 第八

若是遞歸有返回值,將這個值保存下來放在臨時變量裏面,好比retVal當循環結束時,這個臨時變量的值就是整個遞歸處理的結果。

 1 // Recursive Function "Eighth rule" example
 2 int SomeFunc(int n, int &retIdx)
 3 {
 4    ...
 5    if(n>0)
 6    {
 7       int test = SomeFunc(n-1, retIdx);
 8       test--;
 9       ...
10       return test;
11    }
12    ...
13    return 0;
14 }
15 
16 // Conversion to Iterative Function
17 int SomeFuncLoop(int n, int &retIdx)
18 {
19      // (First rule)
20     struct SnapShotStruct {
21        int n;        // - parameter input
22        int test;     // - local variable that will be used 
23                      //     after returning from the function call
24                      // - retIdx can be ignored since it is a reference.
25        int stage;    // - Since there is process needed to be done 
26                      //     after recursive call. (Sixth rule)
27     };
28     // (Second rule)
29     int retVal = 0;  // initialize with default returning value
30     // (Third rule)
31     stack<SnapShotStruct> snapshotStack;
32     // (Fourth rule)
33     SnapShotStruct currentSnapshot;
34     currentSnapshot.n= n;          // set the value as parameter value
35     currentSnapshot.test=0;        // set the value as default value
36     currentSnapshot.stage=0;       // set the value as initial stage
37     snapshotStack.push(currentSnapshot);
38     // (Fifth rule)
39     while(!snapshotStack.empty())
40     {
41        currentSnapshot=snapshotStack.top();
42        snapshotStack.pop();
43        // (Sixth rule)
44        switch( currentSnapshot.stage)
45        {
46        case 0:
47           // (Seventh rule)
48           if( currentSnapshot.n>0 )
49           {
50              ...
51           }
52           ...
53           // (Eighth rule)
54           retVal = 0 ;
55           ...
56           break; 
57        case 1: 
58           // (Seventh rule)
59           currentSnapshot.test = retVal;
60           currentSnapshot.test--;
61           ...
62           // (Eighth rule)
63           retVal = currentSnapshot.test;
64           ...
65           break;
66        }
67     }
68     // (Second rule)
69     return retVal;
70 }
View Code

 第九

若是遞歸函數有「return」關鍵字,你應該在while循環裏面用「continue」代替。若是return了一個返回值,你應該在循環裏面保存下來(步驟8),而後return大部分狀況下,步驟九是可選的,可是它能幫助你避免邏輯錯誤。

 1 // Recursive Function "Ninth rule" example
 2 int SomeFunc(int n, int &retIdx)
 3 {
 4    ...
 5    if(n>0)
 6    {
 7       int test = SomeFunc(n-1, retIdx);
 8       test--;
 9       ...
10       return test;
11    }
12    ...
13    return 0;
14 }
15 
16 // Conversion to Iterative Function
17 int SomeFuncLoop(int n, int &retIdx)
18 {
19      // (First rule)
20     struct SnapShotStruct {
21        int n;        // - parameter input
22        int test;     // - local variable that will be used 
23                      //     after returning from the function call
24                      // - retIdx can be ignored since it is a reference.
25        int stage;    // - Since there is process needed to be done 
26                      //     after recursive call. (Sixth rule)
27     };
28     // (Second rule)
29     int retVal = 0;  // initialize with default returning value
30     // (Third rule)
31     stack<SnapShotStruct> snapshotStack;
32     // (Fourth rule)
33     SnapShotStruct currentSnapshot;
34     currentSnapshot.n= n;          // set the value as parameter value
35     currentSnapshot.test=0;        // set the value as default value
36     currentSnapshot.stage=0;       // set the value as initial stage
37     snapshotStack.push(currentSnapshot);
38     // (Fifth rule)
39     while(!snapshotStack.empty())
40     {
41        currentSnapshot=snapshotStack.top();
42        snapshotStack.pop();
43        // (Sixth rule)
44        switch( currentSnapshot.stage)
45        {
46        case 0:
47           // (Seventh rule)
48           if( currentSnapshot.n>0 )
49           {
50              ...
51           }
52           ...
53           // (Eighth rule)
54           retVal = 0 ;
55           
56           // (Ninth rule)
57           continue;
58           break; 
59        case 1: 
60           // (Seventh rule)
61           currentSnapshot.test = retVal;
62           currentSnapshot.test--;
63           ...
64           // (Eighth rule)
65           retVal = currentSnapshot.test;
66 
67           // (Ninth rule)
68           continue;
69           break;
70        }
71     }
72     // (Second rule)
73     return retVal;
74 }
View Code

 第十

爲了模擬下一次遞歸函數的調用,你必須在當前循環函數裏面再生成一個新的「Snapshot」結構體做爲下一次調用的快照,初始化其參數之後壓入棧,並「continue」。若是當前調用在執行完成後還有一些事情須要處理,那麼更改它的階段狀態「stage」到相應的過程,並在new Snapshot壓入以前,把本次的「Snapshot」壓入。

 1 // Recursive Function "Tenth rule" example
 2 int SomeFunc(int n, int &retIdx)
 3 {
 4    ...
 5    if(n>0)
 6    {
 7       int test = SomeFunc(n-1, retIdx);
 8       test--;
 9       ...
10       return test;
11    }
12    ...
13    return 0;
14 }
15 
16 // Conversion to Iterative Function
17 int SomeFuncLoop(int n, int &retIdx)
18 {
19      // (First rule)
20     struct SnapShotStruct {
21        int n;        // - parameter input
22        int test;     // - local variable that will be used 
23                      //     after returning from the function call
24                      // - retIdx can be ignored since it is a reference.
25        int stage;    // - Since there is process needed to be done 
26                      //     after recursive call. (Sixth rule)
27     };
28     // (Second rule)
29     int retVal = 0;  // initialize with default returning value
30     // (Third rule)
31     stack<SnapShotStruct> snapshotStack;
32     // (Fourth rule)
33     SnapShotStruct currentSnapshot;
34     currentSnapshot.n= n;          // set the value as parameter value
35     currentSnapshot.test=0;        // set the value as default value
36     currentSnapshot.stage=0;       // set the value as initial stage
37     snapshotStack.push(currentSnapshot);
38     // (Fifth rule)
39     while(!snapshotStack.empty())
40     {
41        currentSnapshot=snapshotStack.top();
42        snapshotStack.pop();
43        // (Sixth rule)
44        switch( currentSnapshot.stage)
45        {
46        case 0:
47           // (Seventh rule)
48           if( currentSnapshot.n>0 )
49           {
50              // (Tenth rule)
51              currentSnapshot.stage = 1;            // - current snapshot need to process after
52                                                    //     returning from the recursive call
53              snapshotStack.push(currentSnapshot);  // - this MUST pushed into stack before 
54                                                    //     new snapshot!
55              // Create a new snapshot for calling itself
56              SnapShotStruct newSnapshot;
57              newSnapshot.n= currentSnapshot.n-1;   // - give parameter as parameter given
58                                                    //     when calling itself
59                                                    //     ( SomeFunc(n-1, retIdx) )
60              newSnapshot.test=0;                   // - set the value as initial value
61              newSnapshot.stage=0;                  // - since it will start from the 
62                                                    //     beginning of the function, 
63                                                    //     give the initial stage
64              snapshotStack.push(newSnapshot);
65              continue;
66           }
67           ...
68           // (Eighth rule)
69           retVal = 0 ;
70           
71           // (Ninth rule)
72           continue;
73           break; 
74        case 1: 
75           // (Seventh rule)
76           currentSnapshot.test = retVal;
77           currentSnapshot.test--;
78           ...
79           // (Eighth rule)
80           retVal = currentSnapshot.test;
81           // (Ninth rule)
82           continue;
83           break;
84        }
85     }
86     // (Second rule)
87     return retVal;
88 }
View Code

 

5 替代過程的幾個簡單例子

如下幾個例子均在vs2008環境下開發,主要包含了:

(1)線性遞歸

  1 #ifndef __LINEAR_RECURSION_H__
  2 #define __LINEAR_RECURSION_H__
  3 
  4 #include <stack>
  5 using namespace std;
  6 
  7 /**
  8 * \brief 求n的階乘
  9 * \para 
 10 * \return 
 11 * \note result = n! 遞歸實現
 12 */
 13 int Fact(long n)
 14 {
 15     if(0>n)
 16         return -1;
 17     if(0 == n)
 18         return 1;
 19     else
 20     {
 21         return ( n* Fact(n-1));
 22     }
 23 } 
 24 
 25 /**
 26 * \brief 求n的階乘
 27 * \para 
 28 * \return 
 29 * \note result = n! 循環實現
 30 */
 31 int FactLoop(long n)
 32 {
 33     // (步驟1)
 34     struct SnapShotStruct // 快照結構體局部聲明 
 35     {
 36         long inputN;      // 會改變的參數
 37                           // 沒有局部變量
 38         int stage;        // 階段變量用於快照跟蹤
 39     } ;
 40 
 41     // (步驟2)
 42     int returnVal;        // 用於保存當前調用返回值  
 43 
 44     // (步驟3)
 45     stack<SnapShotStruct> snapshotStack;
 46 
 47     // (步驟4)
 48     SnapShotStruct currentSnapshot;
 49     currentSnapshot.inputN=n;
 50     currentSnapshot.stage=0; // 階段變量初始化
 51 
 52     snapshotStack.push(currentSnapshot);  
 53 
 54     // (步驟5)
 55     while(!snapshotStack.empty())  
 56     {     
 57         currentSnapshot=snapshotStack.top();         
 58         snapshotStack.pop();       
 59 
 60         // (步驟6)
 61         switch(currentSnapshot.stage)
 62         {
 63             // (步驟7)
 64         case 0:
 65             if(0>currentSnapshot.inputN)
 66             {
 67                 // (步驟8 & 步驟9)
 68                 returnVal = -1;
 69                 continue;
 70             }
 71             if(0 == currentSnapshot.inputN)
 72             {
 73                 // (步驟8 & 步驟9)
 74                 returnVal = 1;     
 75                 continue;
 76             }
 77             else
 78             {
 79                 // (步驟10)
 80 
 81                 // 返回 ( n* Fact(n-1)); 分爲2步: 
 82                 // (第一步調用自身,第二步用返回值乘以當前n值)
 83                 // 這裏咱們拍下快照.
 84                 currentSnapshot.stage=1; // 當前的快照表示正在被處理,並等待自身調用結果返回,因此賦值爲1 
 85 
 86                 snapshotStack.push(currentSnapshot);
 87 
 88                 // 建立一個新的快照,用於調用自身
 89                 SnapShotStruct newSnapshot;
 90                 newSnapshot.inputN= currentSnapshot.inputN -1 ; // 初始化參數 
 91                                                                  
 92                 newSnapshot.stage = 0 ;                         // 從頭開始執行自身,因此賦值stage==0 
 93                                                                 
 94                 snapshotStack.push(newSnapshot);
 95                 continue;
 96 
 97             }
 98             break;
 99             // (步驟7)
100         case 1:
101 
102             // (步驟8)
103 
104             returnVal = currentSnapshot.inputN * returnVal;
105 
106             // (步驟9)
107             continue;
108             break;
109         }
110     }
111     
112     // (步驟2)
113     return returnVal;
114 }   
115 #endif //__LINEAR_RECURSION_H__
View Code

(2)二分遞歸

  1 #ifndef __BINARY_RECURSION_H__
  2 #define __BINARY_RECURSION_H__
  3 
  4 #include <stack>
  5 using namespace std;
  6 
  7 /**
  8 * \function FibNum
  9 * \brief 求斐波納契數列
 10 * \para 
 11 * \return 
 12 * \note 遞歸實現
 13 */
 14 int FibNum(int n)
 15 {
 16     if (n < 1)
 17         return -1;
 18     if (1 == n || 2 == n)
 19         return 1;
 20 
 21     // 這裏能夠當作是
 22     //int addVal = FibNum( n - 1);
 23     // addVal += FibNum(n - 2);
 24     // return addVal;
 25     return FibNum(n - 1) + FibNum(n - 2);                             
 26 } 
 27 /**
 28 * \function FibNumLoop
 29 * \brief 求斐波納契數列
 30 * \para 
 31 * \return 
 32 * \note 循環實現
 33 */
 34 int FibNumLoop(int n)
 35 {
 36     // (步驟1)
 37     struct SnapShotStruct // 快照結構體局部聲明 
 38     {
 39         int inputN;       // 會改變的參數
 40         int addVal;       // 局部變量
 41         int stage;        // 階段變量用於快照跟蹤
 42 
 43     };
 44 
 45     // (步驟2)
 46     int returnVal;        // 用於保存當前調用返回值
 47 
 48     // (步驟3)
 49     stack<SnapShotStruct> snapshotStack;
 50 
 51     // (步驟4)
 52     SnapShotStruct currentSnapshot;
 53     currentSnapshot.inputN=n;
 54     currentSnapshot.stage=0; // 階段變量初始化
 55 
 56     snapshotStack.push(currentSnapshot);
 57 
 58     // (步驟5)
 59     while(!snapshotStack.empty())
 60     {
 61         currentSnapshot=snapshotStack.top();
 62         snapshotStack.pop();
 63 
 64         // (步驟6)
 65         switch(currentSnapshot.stage)
 66         {
 67             // (步驟7)
 68         case 0:
 69             if(currentSnapshot.inputN<1)
 70             {
 71                 // (步驟8 & 步驟9)
 72                 returnVal = -1;
 73                 continue;
 74             }
 75             if(currentSnapshot.inputN == 1 || currentSnapshot.inputN == 2 )
 76             {
 77                 // (步驟8 & 步驟9)
 78                 returnVal = 1;
 79                 continue;
 80             }
 81             else
 82             {
 83                 // (步驟10)
 84 
 85                 // 返回 ( FibNum(n - 1) + FibNum(n - 2)); 至關於兩步
 86                 // (第一次調用參數是 n-1, 第二次調用參數 n-2)
 87                 // 這裏咱們拍下快照,分紅2個階段
 88                 currentSnapshot.stage=1;                        // 當前的快照表示正在被處理,並等待自身調用結果返回,因此賦值爲1 
 89 
 90                 snapshotStack.push(currentSnapshot);
 91 
 92                 // 建立一個新的快照,用於調用自身
 93                 SnapShotStruct newSnapshot;
 94                 newSnapshot.inputN= currentSnapshot.inputN -1 ; //初始化參數 FibNum(n - 1)
 95 
 96                 newSnapshot.stage = 0 ;                         
 97                 snapshotStack.push(newSnapshot);
 98                 continue;
 99 
100             }
101             break;
102             // (步驟7)
103         case 1:
104 
105             // (步驟10)
106 
107             currentSnapshot.addVal = returnVal;
108             currentSnapshot.stage=2;                         // 當前的快照正在被處理,並等待的自身調用結果,因此階段變量變成2
109 
110             snapshotStack.push(currentSnapshot);
111 
112             // 建立一個新的快照,用於調用自身
113             SnapShotStruct newSnapshot;
114             newSnapshot.inputN= currentSnapshot.inputN - 2 ; // 初始化參數 FibNum(n - 2)
115             newSnapshot.stage = 0 ;                          // 從頭開始執行,階段變量賦值爲0 
116                                                              
117             snapshotStack.push(newSnapshot);
118             continue;
119             break;
120         case 2:
121             // (步驟8)
122             returnVal = currentSnapshot.addVal + returnVal;  // actual addition of ( FibNum(n - 1) + FibNum(n - 2) )
123 
124             // (步驟9)
125             continue;
126             break;
127         }
128     }  
129 
130     // (步驟2)
131     return returnVal;
132 }
133   
134 
135 #endif //__BINARY_RECURSION_H__
View Code

(3)尾遞歸

 1 #ifndef __TAIL_RECURSION_H__
 2 #define __TAIL_RECURSION_H__
 3 
 4 #include <stack>
 5 using namespace std;
 6 
 7 /**
 8 * \function FibNum2
 9 * \brief 2階裴波那契序列
10 * \para
11 * \return
12 * \note 遞歸實現  f0 = x, f1 = y, fn=fn-1+fn-2,   n=k,k+1,...
13 */ 
14 int FibNum2(int n, int x, int y)
15 {   
16     if (1 == n)    
17     {
18         return y;
19     }
20     else    
21     {
22         return FibNum2(n-1, y, x+y);
23     }
24 }
25 /**
26 * \function FibNum2Loop
27 * \brief 2階裴波那契序列
28 * \para
29 * \return
30 * \note 循環實現 在尾遞歸中, 遞歸調用後除了返回沒有任何其它的操做, 因此在變爲循環時,不須要stage變量
31 */ 
32 int FibNum2Loop(int n, int x, int y)
33 {
34     // (步驟1)
35     struct SnapShotStruct 
36     {
37         int inputN;    // 會改變的參數
38         int inputX;    // 會改變的參數
39         int inputY;    // 會改變的參數
40                        // 沒有局部變量
41     };
42 
43     // (步驟2)
44     int returnVal;
45 
46     // (步驟3)
47     stack<SnapShotStruct> snapshotStack;
48 
49     // (步驟4)
50     SnapShotStruct currentSnapshot;
51     currentSnapshot.inputN = n;
52     currentSnapshot.inputX = x;
53     currentSnapshot.inputY = y;
54 
55     snapshotStack.push(currentSnapshot);
56 
57     // (步驟5)
58     while(!snapshotStack.empty())
59     {
60         currentSnapshot=snapshotStack.top();
61         snapshotStack.pop();
62 
63         if(currentSnapshot.inputN == 1)
64         {
65             // (步驟8 & 步驟9)
66             returnVal = currentSnapshot.inputY;
67             continue;
68         }
69         else
70         {
71             // (步驟10)
72 
73             // 建立新快照
74             SnapShotStruct newSnapshot;
75             newSnapshot.inputN= currentSnapshot.inputN -1 ; // 初始化,調用( FibNum(n-1, y, x+y) )
76             newSnapshot.inputX= currentSnapshot.inputY;
77             newSnapshot.inputY= currentSnapshot.inputX + currentSnapshot.inputY;
78             snapshotStack.push(newSnapshot);
79             continue;
80         }
81     }
82     // (步驟2)
83     return returnVal;
84 } 
85 
86 #endif //__TAIL_RECURSION_H__
View Code

(4)互遞歸

  1 #ifndef __MUTUAL_RECURSION_H__
  2 #define __MUTUAL_RECURSION_H__
  3 #include <stack>
  4 using namespace std;
  5 
  6 bool IsEvenNumber(int n);//判斷是不是偶數
  7 bool IsOddNumber(int n);//判斷是不是奇數
  8 bool isOddOrEven(int n, int stage);//判斷是不是奇數或偶數
  9 
 10 /****************************************************/
 11 //互相調用的遞歸實現
 12 bool IsOddNumber(int n)
 13 {
 14     // 終止條件
 15     if (0 == n)
 16         return false;
 17     else
 18         // 互相調用函數的遞歸調用
 19         return IsEvenNumber(n - 1);
 20 }
 21 
 22 bool IsEvenNumber(int n)
 23 {
 24     // 終止條件
 25     if (0 == n)
 26         return true;
 27     else
 28         // 互相調用函數的遞歸調用
 29         return IsOddNumber(n - 1);
 30 } 
 31 
 32 
 33 /*************************************************/
 34 //互相調用的循環實現
 35 bool IsOddNumberLoop(int n)
 36 {
 37     return isOddOrEven(n , 0);
 38 }
 39 
 40 bool IsEvenNumberLoop(int n)
 41 {
 42     return isOddOrEven(n , 1);
 43 }
 44 
 45 bool isOddOrEven(int n, int stage)
 46 {
 47     // (步驟1)
 48     struct SnapShotStruct
 49     {
 50         int inputN;       // 會改變的參數
 51         int stage;
 52                           // 沒有局部變量
 53     };
 54 
 55     // (步驟2)
 56     bool returnVal;       
 57 
 58     // (步驟3)
 59     stack<SnapShotStruct> snapshotStack;
 60 
 61     // (步驟4)
 62     SnapShotStruct currentSnapshot;
 63     currentSnapshot.inputN = n;
 64     currentSnapshot.stage = stage;
 65 
 66     snapshotStack.push(currentSnapshot);
 67 
 68     // (步驟5)
 69     while(!snapshotStack.empty())
 70     {
 71         currentSnapshot=snapshotStack.top();
 72         snapshotStack.pop();
 73 
 74         // (步驟6)
 75         switch(currentSnapshot.stage)
 76         {
 77             // (步驟7)
 78             // bool IsOddNumber(int n)
 79         case 0:
 80             // 終止條件
 81             if (0 == currentSnapshot.inputN)
 82             {
 83                 // (步驟8 & 步驟9)
 84                 returnVal = false;
 85                 continue;
 86             }
 87             else
 88             {
 89                 // (步驟10)
 90 
 91                 // 模擬互調用的遞歸調用
 92 
 93                 // 建立新的快照
 94                 SnapShotStruct newSnapshot;
 95                 newSnapshot.inputN= currentSnapshot.inputN - 1; // 初始化參數 
 96                 // 調用 ( IsEvenNumber(n - 1) )
 97                 newSnapshot.stage= 1;
 98                 snapshotStack.push(newSnapshot);
 99                 continue;
100             }
101 
102             break;
103             // (步驟7)
104             // bool IsEvenNumber(int n)
105         case 1:
106             // 終止條件
107             if (0 == currentSnapshot.inputN)
108             {
109                 // (步驟8 & 步驟9)
110                 returnVal = true;
111                 continue;
112             }
113             else
114             {
115                 // (步驟10)
116 
117                 // 模擬互調用的遞歸調用
118 
119                 // 建立新的快照
120                 SnapShotStruct newSnapshot;
121                 newSnapshot.inputN= currentSnapshot.inputN - 1; // 
122                 // calling itself ( IsEvenNumber(n - 1) )
123                 newSnapshot.stage= 0;
124                 snapshotStack.push(newSnapshot);
125                 continue;
126             }
127             break;
128         }
129 
130     }
131     // (步驟2)
132     return returnVal;
133 }  
134 
135 #endif //__MUTUAL_RECURSION_H__
View Code

(5)嵌套遞歸

  1 #ifndef __NESTED_RECURSION_H__
  2 #define __NESTED_RECURSION_H__
  3 #include <stack>
  4 using namespace std;
  5 
  6 int Ackermann(int x, int y)
  7 {
  8     // 終止條件
  9     if (0 == x)
 10     {
 11         return y + 1;
 12     }   
 13     // 錯誤處理條件
 14     if (x < 0  ||  y < 0)
 15     {
 16         return -1;
 17     }  
 18     // 線性方法的遞歸調用 
 19     else if (x > 0 && 0 == y) 
 20     {
 21         return Ackermann(x-1, 1);
 22     }
 23     // 嵌套方法的遞歸調用
 24     else
 25     {
 26         //能夠當作是:
 27         // int midVal = Ackermann(x, y-1);
 28         // return Ackermann(x-1, midVal);
 29         return Ackermann(x-1, Ackermann(x, y-1));
 30     }
 31 }
 32 
 33 
 34 
 35 int AckermannLoop(int x, int y)
 36 {
 37     // (步驟1)
 38     struct SnapShotStruct 
 39     {
 40         int inputX;       // 會改變的參數
 41         int inputY;       // 會改變的參數
 42         int stage;
 43                           // 沒有局部變量
 44     };
 45 
 46     // (步驟2)
 47     int returnVal;        
 48 
 49     // (步驟3)
 50     stack<SnapShotStruct> snapshotStack;
 51 
 52     // (步驟4)
 53     SnapShotStruct currentSnapshot;
 54     currentSnapshot.inputX = x;
 55     currentSnapshot.inputY = y;
 56     currentSnapshot.stage = 0;
 57 
 58     snapshotStack.push(currentSnapshot);
 59 
 60     // (步驟5)
 61     while(!snapshotStack.empty())
 62     {
 63         currentSnapshot=snapshotStack.top();
 64         snapshotStack.pop();
 65 
 66         // (步驟6)
 67         switch(currentSnapshot.stage)
 68         {
 69             // (步驟7)
 70         case 0:
 71             // 終止條件
 72             if(currentSnapshot.inputX == 0)
 73             {
 74                 // (步驟8 & 步驟9)
 75                 returnVal = currentSnapshot.inputY + 1;
 76                 continue;             // 這裏必須返回
 77             }
 78             // 錯誤處理條件        
 79             if (currentSnapshot.inputX < 0  ||  currentSnapshot.inputY < 0)
 80             {
 81                 // (步驟8 & 步驟9)
 82                 returnVal = -1;
 83                 continue;             // 這裏必須返回
 84             }  
 85             // 線性方法的遞歸調用 
 86             else if (currentSnapshot.inputX > 0 && 0 == currentSnapshot.inputY) 
 87             {
 88                 // (步驟10)
 89 
 90                 // 建立新快照
 91                 SnapShotStruct newSnapshot;
 92                 newSnapshot.inputX= currentSnapshot.inputX - 1; // 參數設定 calling itself ( Ackermann(x-1, 1) )
 93                 newSnapshot.inputY= 1;                          // 參數設定 calling itself ( Ackermann(x-1, 1) )
 94                 newSnapshot.stage= 0;
 95                 snapshotStack.push(newSnapshot);
 96                 continue;
 97             }
 98             // Recursive call by Nested method
 99             else
100             {
101                 // (步驟10)
102 
103                 currentSnapshot.stage=1;                       
104                 snapshotStack.push(currentSnapshot);
105 
106                 // 建立新快照
107                 SnapShotStruct newSnapshot;
108                 newSnapshot.inputX= currentSnapshot.inputX;        //參數設定calling itself ( Ackermann(x, y-1) )
109                 newSnapshot.inputY= currentSnapshot.inputY - 1; //參數設定calling itself ( Ackermann(x, y-1) )
110                 newSnapshot.stage = 0;
111                 snapshotStack.push(newSnapshot);
112                 continue;
113             }
114             break;
115         case 1:
116             // (步驟10)
117 
118             // 建立新快照
119             SnapShotStruct newSnapshot;
120             newSnapshot.inputX= currentSnapshot.inputX - 1;   // 設定參數calling itself ( Ackermann(x-1,  Ackermann(x, y-1)) )
121             newSnapshot.inputY= returnVal;                    // 設定參數calling itself ( Ackermann(x-1,  Ackermann(x, y-1)) )
122             newSnapshot.stage = 0;
123             snapshotStack.push(newSnapshot);
124             continue;
125             break;
126         }
127     }
128     // (步驟2)
129     return returnVal;
130 }     
131 #endif //__NESTED_RECURSION_H__
View Code

測試代碼:

 1 #include <tchar.h>
 2 #include "BinaryRecursion.h"
 3 #include "LinearRecursion.h"
 4 #include "MutualRecursion.h"
 5 #include "NestedRecursion.h"
 6 #include "TailRecursion.h"
 7 
 8 
 9 int _tmain(int argc,_TCHAR argv[] )
10 {
11     // Binary Recursion
12     int result = FibNum(10);
13     int result2 = FibNumLoop(10);
14 
15     printf("FibNum(10) = %d\n",result);
16     printf("FibNumLoop(10) = %d\n",result2);
17 
18 
19     // Linear Recursion
20     result = Fact(10);
21     result2 = FactLoop(10);
22 
23     printf("Fact(10) = %d\n",result);
24     printf("FactLoop(10) = %d\n",result2);
25 
26 
27     // Tail Recursion
28     result = FibNum2(10,5,4);
29     result2 = FibNum2Loop(10,5,4);
30 
31     printf("FibNum2(10,5,4) = %d\n",result);
32     printf("FibNumLoop2(10,5,4) = %d\n",result2);
33 
34 
35     // Mutual Recursion
36     bool bResult = IsOddNumber(10);
37     bool bResult2 = IsOddNumberLoop(10);
38 
39     bool bResult3 = IsEvenNumber(10);
40     bool bResult4 = IsEvenNumberLoop(10);
41 
42     printf("IsOddNumber(10) = %d\n",(int)bResult);
43     printf("IsOddNumberLoop(10) = %d\n",(int)bResult2);
44     printf("IsEvenNumber(10) = %d\n",(int)bResult3);
45     printf("IsEvenNumberLoop(10) = %d\n",(int)bResult4);
46 
47 
48     // Nested Recursion
49     result = Ackermann(3,2);
50     result2 = AckermannLoop(3,2);
51 
52     printf("Ackermann(3,2) = %d\n",result);
53     printf("AckermannLoop(3,2) = %d\n",result2);
54 
55     while(1){}
56     return 0;
57 }
View Code

 

6 更多的例子

7 結論

個人結論就是在c/c++或者Java代碼中,儘可能避免用遞歸。可是正如你看到的,遞歸容易理解,可是容易致使棧溢出。雖然循環版本的函數不會增長代碼可讀性和提高性能,可是它能有效的避免衝突或未定義行爲。正如我開頭所說,個人作法一般是在代碼中寫兩份代碼,一份遞歸,一份循環的。前者用於理解代碼,後者用於實際的運行和測試用。若是你對於本身代碼中使用這兩種代碼的利弊很清楚,你能夠選擇你本身的方式。

8 參考

9 License

本文及包含的代碼聽從協議 The MIT License
 
 
 ************************************************************************************************************

原文:http://www.codeproject.com/Articles/418776/How-to-replace-recursive-functions-using-stack-and

以上就是原文的一些內容,感謝原做者Woong Gyu La

這篇文章中的代碼我在調式過程當中,發現了一個問題:循環版本的函數在執行效率方面存在問題。之後再改

相關文章
相關標籤/搜索