漢諾塔的非遞歸算法
阿新 • • 發佈:2017-09-06
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; }
漢諾塔的非遞歸算法