1. 程式人生 > >漢諾塔的非遞歸算法

漢諾塔的非遞歸算法

tac color clu 棧操作 std 表示 esp ont -s

思路

模擬遞歸程序執行過程,借助一個堆棧,把遞歸轉成非遞歸算法。

轉化過程

1. 遞歸算法

  

1 void hanoi(int n, char from, char pass, char to) {
2         if (n == 0)
3                 return;
4 
5         hanoi(n - 1, from, to, pass);
6         move(n, from, to);
7         hanoi(n - 1, pass, from, to);
8 }

2. 處理首遞歸

  本函數第2行是結束條件,第5行開始進入首遞歸。執行第5行函數調用之前,需要保留調用現場,本例中是4個參數入棧,使用新的參數調用hanoi函數。而繼續跟蹤被調用的函數,可以看出需要一直進行入棧操作,直到參數n == 0為止。 對此行為,我們可以用一個循環來模擬:

  

偽代碼:
int
i = n; while (i != 0) {
 push(i);
i --; }

  現在可以斷言 i ==0 ,滿足函數返回的條件。當函數返回後,需要通過pop操作來恢復現場,然後繼續執行後面的語句。為了簡化問題,我們假設後面只有move()一條語句,執行完畢該語句後就繼續向上一層回溯,直至最頂層, 這樣又可以用一個循環來模擬:

偽代碼:
int i = n;
while (i != 0) {
 push(i);
  i --;
}
while (棧不空) {
  int m = pop();
  move(m, ...);
  尾遞歸...
}

3. 處理尾遞歸

  一般而言,尾遞歸可以直接改成上一條語句的循環。但在本例中,尾遞歸被嵌在另一個循環中,此時需要模擬它的行為:進入尾遞歸hanoi()函數後,需要執行其函數體,而函數體又是上述代碼中的第一句話,可以如下表示:

偽代碼:

a_new_beginning: 
    int i = n;
    while (i != 0) {
     push(i);
      i --;
    }
    while (棧不空) {
      int m = pop();
      move(m, ...);

      產生新的參數,跳出循環,跳轉到a_new_beginning語句處;
    }

  相比於第一個while全部執行,第二個while實際只被執行一次就跳出來了,這種結構,很顯然可以等價變換為外加一個大循環。那麽在外部的大循環的作用下,第二個while循環可以“降維”, 去掉一層循環, 並根據pop()的現場來產生新的一批參數,給下一次循環使用。註意,兩層循環合並後,循環終止條件也需要進行合並。

偽代碼:
while (!(n == 0 && 棧空))  // 內外兩層終止條件進行合並
    int i = n;
    while (i != 0) {
     push(i);
      i --;
    }
    int m = pop();
    move(m, ...);

    //產生新的參數
    n = m;
    n --;  // 對應hanoi(n - 1, pass, ...)
}

  最後進行完善,加上其他參數的變換。

源代碼

#include <iostream>
#include <stack>
using namespace std;


void move(int n, char from, char to) {
        cout << "from " << from << " move " << n << " to " << to << endl;
}


struct param {
        int n;
        char from;
        char pass;
        char to;
};

void hanoi(int n, char from, char pass, char to) {
        stack<param> s;
        param par_outer = {n, from, pass, to};
        while (!(par_outer.n == 0 && s.empty())) {
                param par_inner = par_outer;
                while (par_inner.n > 0) {
                        s.push(par_inner);
                        par_inner.n --;
                        swap(par_inner.pass, par_inner.to);
                }

                par_outer = s.top();
                s.pop();

                move(par_outer.n, par_outer.from, par_outer.to);

                par_outer.n --;
                swap(par_outer.from, par_outer.pass);
        }
}


int main() {
        int N = 5;
        hanoi(N, A, B, C);

        return 0;
}

漢諾塔的非遞歸算法