汉诺塔的非递归算法
思路
模拟递归程序执行过程,借助一个堆栈,把递归转成非递归算法。
转化过程
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; }