开头
学习c++到了递归这个地方,遇到了汉诺塔的问题,个人理解上感觉这个问题有点像动态规划,都是把问题去不断地分解,递归这里反复调用同一个方法来实现解决问题的操作,如果我要解决最终一个问题,那么我就把这个问题推导到它之前一步的问题,也就是说解决这个问题的方法就是将这个问题推导,推导的过程既是解决问题的过程也是连接解决问题不同步骤的过程。把这个过程表示出来然后不断的去引用直到一个终点(某一个标志),终点问题被解决然后不断地回溯直到最开始的问题。
汉诺塔问题Fig1
1.问题分解
汉诺塔问题就是把第一个杆上的圆盘全部挪到另外一个杆子上去,一次只能挪一个而且只能小的摞在大的上面。例如在上面的图里面,要把A柱子上的5个圆盘全都挪到B柱子上去,那么我们可以先把上面4个圆盘当作一个主体我叫它X,最下面一个叫Y,所以操作是stpe1 X:A 到C;step2 Y:A到B;step3 X:C到B。那么就把整体从A移到B推导成为了2个子问题(step1 and step3)和一个中间操作(step2),那么再来解决step1,把四个圆盘移动到C,依然用前面的X,Y的命名方式,那么就需要X移动到B然后Y移动到C,然后X移动到C,最终经过依此的分解就可以得到以下的一个粗略图。Fig2
箭头上的数字是每次移动的个数,这里没有分解完,每一个步骤的第三步都需要再次进行分解,例如第一步的C到B可以分解成为上面三个移动到A然后下面一个移动到B然后上面三个移动回B。通过上面的图也可以看出中间那一列既是操作的中间一步,也是我这一行操作最终完成的效果,比如第一行完成了5个模块从A到B的操作,这也给了我们一个提示那就是递归的终点的问题,递归的终点的时候应该是只有一个模块需要移动,所以中间的一步直接就是模块的移动,不需要再次进行递归的操作。
2.代码实现
对递归的过程以及终点分析后下一步就是代码的实现问题首先大致的框架应该是像下面Fig3
第二行和第四行的递归调用都是解决X的挪动问题,中间一行输出的是Y的挪动。但是注意在递归里面操作的方法都是一样的,在Fg2里面每一个步骤做的操作都是不同的,比如step1做的是A到B,step2做的是A到C的操作,递归的时候只能用一种方法,那么就可以做一个交换,把变量标识符的位置做一个交换,比如现在要移动5个圆盘,程序的第一步是要去调用F(4),在F(4)调用的时候把b和c的标识符对调,那么实际完成的就是从a到b的移动。
通过对字母的排列代表了圆盘的移动,为了能够统一在图二中的不同的step的操作(能用函数去表达和反复调用),这里把3个杆子代表的字母全都写出来:A,B,C,并且通过改变杆子的命名来实现不同的方法的统一,在这里A,B,C就代表了从A到C的操作,也就是中间一步的操作。在这里我也建议像我一样的小白可以按下面的图上的左边展示的关系去理解3根杆子的关系。
当然也可以按照一些教材上写的 把(a,b,c)理解成为从a借过b到c,也就是先把上面的移动到b,然后把下面的移动到c,然后再把上面的移动到c。
突然想啰嗦一点......就是在代码的含义
第一行我要实现5个圆盘从A移动到C的过程,第二行首先要实现从A到B的移动过程,第三行输出了在移动完X从A到B后Y的操作,也就是整个5个圆盘移动的中间操作,第四行X移动回来。这里整个F5这一行也就是这一个函数表示的方法就是 对于排列为A,B,C的字符串,从这么一个排列中,找到第一步要解决的问题(字符串的前两个),找到了这一行要解决的问题(字符串的第一个和第三个)而且也是中间步骤,然后输出,找到了第三步要解决的问题(字符的后两个),并规定了套路和方法和其实际产生的唯一结果(输出中间步骤也就是要解决的问题)。那么我在解决第一步的问题的时候只需要把B,C两个字符的位置在字符串中交换,那么这个方法就被用于解决从A到B移动圆盘的问题,然后再字符串前面加数字表示移动多少个圆盘。直到最后只有1个圆盘的时候就没有其他的步骤,中间一部就可以说明这个方法产生的结果所以可以直接得到输出语句并终止递归调用。
#include
using namespace std;
void HN(int n, char a, char b, char c)
{
if (n >= 1)
{
HN(n - 1, a, c, b);
cout << a << "--" << c << endl;
HN(n - 1, b, a, c);
}
}
int main()
{
int n;
cout << "please input the number of diskes" << endl;
cin >> n;
HN(n, 'A', 'B', 'C');
}
上面是我写的代码。
结尾
罗里吧嗦的写了一堆,也没讲清楚大佬用几句话就能说清的事情,惭愧,继续进步,继续加油吧。