將遞歸轉化成迭代的通用技術

轉自:  http://blog.csdn.net/whinah/article/details/6419680


從理論上講,只要允許使用棧,所有的遞歸程序都可以轉化成迭代。

但是並非所有遞歸都必須用棧,不用堆棧也可以轉化成迭代的,大致有兩類

  1. 尾遞歸:可以通過簡單的變換,讓遞歸作爲最後一條語句,並且僅此一個遞歸調用。

  1. // recursive  
  2. int fac1(int n) {  
  3.     if (n <= 0) return 1;  
  4.     return n * fac1(n-1);  
  5. }  
  6. // iterative  
  7. int fac2(int n) {  
  8.     int i = 0, y = 1;  
  9.     for (; i <= n; ++i) y *= i;  
  10.     return y;  
  11. }  
 

  1. 自頂向下->自底向上:對程序的結構有深刻理解後,自底向上計算,比如 fibnacci 數列的遞歸->迭代轉化。

  1. // recursive, top-down  
  2. int fib1(int n) {  
  3.     if (n <= 1) return 1;  
  4.     return fib1(n-1) + fib1(n-2);  
  5. }  
  6. // iterative, down-top  
  7. int fib2(int n) {  
  8.     int f0 = 1, f1 = 1, i;  
  9.     for (i = 2; i <= n; ++i) {  
  10.         int f2 = f1 + f0;  
  11.         f0 = f1; f1 = f2;  
  12.     }  
  13.     return f1;  
  14. }  
 

 

對於非尾遞歸,就必須使用堆棧。可以簡單生硬地使用堆棧進行轉化:把函數調用和返回的地方翻譯成彙編代碼,然後把對硬件 stack 的  push, pop 操作轉化成對私有 stack 的 push, pop ,這其中需要特別注意的是對返回地址的 push/pop,對應的硬件指令一般是 call/ret。使用私有 stack 有兩個好處:

  1. 可以省去公用局部變量,也就是在任何一次遞歸調用中都完全相同的函數參數,再加上從這些參數計算出來的局部變量。
  2. 如果需要得到當前的遞歸深度,可以從私有 stack 直接拿到,而用遞歸一般需要一個單獨的 depth 變量,然後每次遞歸調用加 1。

我們把私有 stack 元素稱爲 Frame,那麼 Frame 中必須包含以下信息:

  1. 返回地址(對應於每個遞歸調用的下一條語句的地址)
  2. 對每次遞歸調用都不同的參數

通過實際操作,我發現,有一類遞歸的 Frame 可以省去返回地址!所以,這裏又分爲兩種情況:

  • Frame 中可以省去返回地址的遞歸:僅有兩個遞歸調用,並且其中有一個是尾遞歸。

  1. // here used a function 'partition', but don't implement it  
  2. tempalte<class RandIter>  
  3. void QuickSort1(RandIter beg, RandIter end) {  
  4.     if (end - beg <= 1) return;  
  5.     RandIter pos = partition(beg, end);  
  6.     QuickSort1(beg, pos);  
  7.     QuickSort1(pos + 1, end);  
  8. }  
  9. tempalte<class RandIter>  
  10. void QuickSort2(RandIter beg, RandIter end) {  
  11.     std::stack<std::pair<RandIter> > stk;  
  12.     stk.push({beg, end});  
  13.     while (!stk.empty()) {  
  14.         std::pair<RandIter, RandIter> ii = stk.top(); stk.pop();  
  15.         if (ii.second - ii.first) > 1) {  
  16.             RandIter pos = partition(beg, end);  
  17.             stk.push({ii.first, pos});  
  18.             stk.push({pos + 1, ii.second});  
  19.         }  
  20.     }  
  21. }  
 

  • Frame 中必須包含返回地址的遞歸,這個比較複雜,所以我寫了個完整的示例:
    • 以MergeSort爲例,因爲 MergeSort 是個後序過程,兩個遞歸調用中沒有任何一個是尾遞歸
    • MergeSort3 使用了 GCC 的 Label As Value 特性,只能在 GCC 兼容的編譯器中使用
    • 單純對於這個實例來說,返回地址其實只有兩種,返回地址爲 0 的情況可以通過判斷私有棧(varname=stk)是否爲空,stk爲空時等效於 retaddr == 0。如果要精益求精,一般情況下指針的最低位總是0,可以把這個標誌保存在指針的最低位,當然,如此的話就無法對 sizeof(T)==1 的對象如 char 進行排序了。
      1. #include <stdio.h>  
      2. #include <string.h>  
      3. # if 1  
      4. #include <stack>  
      5. #include <vector>  
      6. template<class T>  
      7. class MyStack : public std::stack<T, std::vector<T> >  
      8. {  
      9. };  
      10. #else  
      11. template<class T>  
      12. class MyStack {  
      13.     union {  
      14.         char*  a;  
      15.         T* p;  
      16.     };  
      17.     int n, t;  
      18. public:  
      19.     explicit MyStack(int n=128) {  
      20.         this->n = n;  
      21.         this->t = 0;  
      22.         a = new char[n*sizeof(T)];  
      23.     }  
      24.     ~MyStack() {  
      25.         while (t > 0)  
      26.             pop();  
      27.         delete[] a;  
      28.     }  
      29.     void swap(MyStack<T>& y) {  
      30.         char* q = y.a; y.a = a; a = q;  
      31.         int z;  
      32.         z = y.n; y.n = n; n = z;  
      33.         z = y.t; y.t = t; t = z;  
      34.     }  
      35.     T& top() const {   
      36.         return p[t-1];  
      37.     }  
      38.     void pop() {  
      39.         --t;  
      40.         p[t].~T();  
      41.     }  
      42.     void push(const T& x) {  
      43.         x.print(); // debug  
      44.         p[t] = x;  
      45.         ++t;  
      46.     }  
      47.     int size() const { return t; }  
      48.     bool empty() const { return 0 == t; }  
      49.     bool full() const { return n == t; }  
      50. };  
      51. #endif  
      52. template<class T>  
      53. struct Frame {  
      54.     static T* base;  
      55.     T *beg, *tmp;  
      56.     int len;  
      57.     int retaddr;  
      58.     Frame(T* beg, T* tmp, int len, int retaddr)  
      59.         : beg(beg), tmp(tmp), len(len), retaddr(retaddr)  
      60.     {}  
      61.     void print() const { // for debug  
      62.         printf("%4d %4d %d/n"int(beg-base), len, retaddr);  
      63.     }  
      64. };  
      65. template<class T> T* Frame<T>::base;  
      66. #define TOP(field) stk.top().field  
      67. template<class T>  
      68. bool issorted(const T* a, int n)  
      69. {  
      70.     for (int i = 1; i < n; ++i) {  
      71.         if (a[i-1] > a[i]) return false;  
      72.     }  
      73.     return true;  
      74. }  
      75. template<class T>  
      76. void mymerge(const T* a, int la, const T* b, int lb, T* c) {  
      77.     int i = 0, j = 0, k = 0;  
      78.     for (; i < la && j < lb; ++k) {  
      79.         if (b[j] < a[i])  
      80.             c[k] = b[j], ++j;  
      81.         else  
      82.             c[k] = a[i], ++i;  
      83.     }  
      84.     for (; i < la; ++i, ++k) c[k] = a[i];  
      85.     for (; j < lb; ++j, ++k) c[k] = b[j];  
      86. }  
      87. template<class T>  
      88. void MergeSort1(T* beg, T* tmp, int len) {  
      89.     if (len > 1) {  
      90.         int mid = len / 2;  
      91.         MergeSort1(beg    , tmp    , mid);  
      92.         MergeSort1(beg+mid, tmp+mid, len-mid);  
      93.         mymerge(tmp, mid, tmp+mid, len-mid, beg);  
      94.         memcpy(tmp, beg, sizeof(T)*len);  
      95.     }  
      96.     else  
      97.         *tmp = *beg;  
      98. }  
      99. template<class T>  
      100. void MergeSort2(T* beg0, T* tmp0, int len0) {  
      101.     int mid;  
      102.     int cnt = 0;  
      103.     Frame<T>::base = beg0;  
      104.     MyStack<Frame<T> > stk;  
      105.     stk.push(Frame<T>(beg0, tmp0, len0, 0));  
      106.     while (true) {  
      107.         ++cnt;  
      108.         if (TOP(len) > 1) {  
      109.             mid = TOP(len) / 2;  
      110.             stk.push(Frame<T>(TOP(beg), TOP(tmp), mid, 1));  
      111.             continue;  
      112. L1:  
      113.             mid = TOP(len) / 2;  
      114.             stk.push(Frame<T>(TOP(beg)+mid, TOP(tmp)+mid, TOP(len)-mid, 2));  
      115.             continue;  
      116. L2:  
      117.             mid = TOP(len) / 2;  
      118.             mymerge(TOP(tmp), mid, TOP(tmp)+mid, TOP(len)-mid, TOP(beg));  
      119.             memcpy(TOP(tmp), TOP(beg), sizeof(T)*TOP(len));  
      120.         } else  
      121.             *TOP(tmp) = *TOP(beg);   
      122.         int retaddr0 = TOP(retaddr);  
      123.         stk.pop();  
      124.         switch (retaddr0) {  
      125.         case 0: return;  
      126.         case 1: goto L1;  
      127.         case 2: goto L2;  
      128.         }  
      129.     }  
      130. }  
      131. // This Implementation Use GCC's goto saved label value  
      132. // Very similiar with recursive version  
      133. template<class T>  
      134. void MergeSort3(T* beg0, T* tmp0, int len0) {  
      135. MyEntry:  
      136.     int mid;  
      137.     int retaddr;  
      138.     Frame<T>::base = beg0;  
      139.     MyStack<Frame<T> > stk;  
      140.     stk.push(Frame<T>(beg0, tmp0, len0, 0));  
      141. #define Cat1(a,b) a##b  
      142. #define Cat(a,b) Cat1(a,b)  
      143. #define HereLabel() Cat(HereLable_, __LINE__)  
      144. #define RecursiveCall(beg, tmp, len) /  
      145.     stk.push(Frame<T>(beg, tmp, len, (char*)&&HereLabel() - (char*)&&MyEntry)); /  
      146.     continue; /  
      147.     HereLabel():;  
      148. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
      149. // retaddr == 0 是最外層的遞歸調用,  
      150. // 只要到達這一層時 retaddr 才爲 0,  
      151. // 此時就可以返回了  
      152. #define MyReturn /  
      153.     retaddr = TOP(retaddr); /  
      154.     stk.pop(); /  
      155.     if (0 == retaddr) { /  
      156.         return; /  
      157.     } /  
      158.     goto *((char*)&&MyEntry + retaddr);  
      159. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
      160.     while (true) {  
      161.         if (TOP(len) > 1) {  
      162.             mid = TOP(len) / 2;  
      163.             RecursiveCall(TOP(beg), TOP(tmp), mid);  
      164.             mid = TOP(len) / 2;  
      165.             RecursiveCall(TOP(beg)+mid, TOP(tmp)+mid, TOP(len)-mid);  
      166.             mid = TOP(len) / 2;  
      167.             mymerge(TOP(tmp), mid, TOP(tmp)+mid, TOP(len)-mid, TOP(beg));  
      168.             memcpy(TOP(tmp), TOP(beg), sizeof(T)*TOP(len));  
      169.         } else  
      170.             *TOP(tmp) = *TOP(beg);   
      171.         MyReturn;  
      172.     }  
      173. }  
      174. template<class T>  
      175. void MergeSortDriver(T* beg, int len, void (*mf)(T* beg_, T* tmp_, int len_))  
      176. {  
      177.     T* tmp = new T[len];  
      178.     (*mf)(beg, tmp, len);  
      179.     delete[] tmp;  
      180. }  
      181. #define test(a,n,mf) /  
      182.     memcpy(a, b, sizeof(a[0])*n); /  
      183.     MergeSortDriver(a, n, &mf); /  
      184.     printf("sort by %s:", #mf); /  
      185.     for (i = 0; i < n; ++i) printf("% ld", a[i]); /  
      186.     printf("/n");  
      187. int main(int argc, char* argv[])  
      188. {  
      189.     int n = argc - 1;  
      190.     int i;  
      191.     long* a = new long[n];  
      192.     long* b = new long[n];  
      193.     for (i = 0; i < n; ++i)  
      194.         b[i] = strtol(argv[i+1], NULL, 10);  
      195.     test(a, n, MergeSort1);  
      196.     test(a, n, MergeSort2);  
      197.     test(a, n, MergeSort3);  
      198.     printf("All Successed/n");  
      199.     delete[] a;  
      200.     delete[] b;  
      201.     return 0;  
      202. }  
       
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章