验证汉诺塔策略(Validating the strategy)
尽管伪代码策略看起来是正确的,但到目前为止的我们的推导却显得有点粗心。无论何时使用递归分解问题,必须确保新问题的形式与原始文件相同。我们将N-1个磁盘从一根针移动到另一个跟针的任务肯定听起来像是同样问题的,适合moveTower原型。 即使如此,这里有一个微妙但重要的区别:在原始问题中,目的地和临时针是空的。当我们将N-1大小塔移动到临时针时,作为递归策略的一部分,在开始的针上留下了一个磁盘。 该磁盘的存在是否会改变问题的性质,从而使递归解决方案无效?
要回答这个问题,你需要根据游戏的规则来考虑这个子问题。如果递归分解不会违反规则,一切都应该可以了。第一个规则——一次只能移动一个磁盘,这不是问题。如果存在多个单独的磁盘,则递归分解会将问题解决,从而产生更简单的情况。实际传输磁盘的伪代码中的步骤只能一次移动一个磁盘。 第二条规则——不能将更大的磁盘放在较小的磁盘之上,你需要说服自己,你不会在递归分解中违反这个规则。
要做的重要观察是,当我们将磁盘从一根针移动到另一根针时,我们在开始那根针上留下的磁盘必须比移走的所有的磁盘都要大。因此,当我们将这些磁盘移动到终点针之间时,它们留下的唯一磁盘的大小将更大,这与规则是一致的。这个是关键的规则。
编码解决方案(Coding the solution)
要完成汉诺塔的解决方案,唯一剩下的一个步骤是替换函数调用剩余的伪代码。移动一个完整的塔的任务需要递归调用moveTower函数。唯一的其他操作是将单个磁盘从一根针移动到另一个。为了编写解决方案中的步骤的测试程序,我们需要的是在控制台上记录其操作的功能。例如,您可以执行moveSingleDisk函数,如下所示:
void moveSingleDisk(char start, char finish) {
cout << start << " -> " << finish << endl;
}
而moveTower则看起来是这样:
void moveTower(int n, char start, char finish, char tmp) {
if (n == 1) {
moveSingleDisk(start, finish);
} else {
moveTower(n - 1, start, tmp, finish);
moveSingleDisk(start, finish);
moveTower(n - 1, tmp, finish, start);
}
}
解决代码如下
#include <iostream>
using namespace std;
/* 函数原型 */
void moveTower(int n, char start, char finish, char tmp);
void moveSingleDisk(char start, char finish);
/* 主函数 */
int main() {
int n;
cout << "输入需要搬运的盘子总数: " << endl;
cin >> n;
moveTower(n, 'A', 'B', 'C');
return 0;
}
/*移动n个盘子,并以tmp作为中转*/
void moveTower(int n, char start, char finish, char tmp) {
if (n == 1) {
moveSingleDisk(start, finish);
} else {
moveTower(n - 1, start, tmp, finish);
moveSingleDisk(start, finish);
moveTower(n - 1, tmp, finish, start);
}
}
//用来记录移动的操作,并在控制台显示出来
void moveSingleDisk(char start, char finish) {
cout << start << " -> " << finish << endl;
}
结果运行如下:
PS:这个内容本来后面还有个分析程序的运行过程的,但是那里也说了,如果你相信leap of faith,就可以跳过那部分内容,其实分析的过程跟C++抽象编程——递归简介(2)——阶乘函数的执行分析是一样的。