今天终于学到了传说中递归入门的经典例子:汉诺塔问题,想想还真有点害怕呢,本来以为用C语言实现汉诺塔问题估计要好多行,但是事实上实现代码只有十几行,真心震撼啊。
汉诺塔问题中move函数代码:
void move(intn, char x, char y, char z)
{
if(n==1) { printf("%c-->%c\n", x,z);
}
else {move(n-1, x, z, y);
// 将 n-1 个盘子从 x 借助 z 移到 y 上
printf("%c-->%c\n",x, z);
// 将 第 n 个盘子从 x 移到 z 上
move(n-1, y, x, z);
// 将 n-1 个盘子从 y 借助 x 移到 z 上
}
}
不过我真的很好奇这个代码的组成结构为什么这么奇怪,于是通过模拟汉诺塔的游戏研究了一下,下面是研究的一些成果。
首先,我把研究对象定为两个盘的汉诺塔问题,结果我发现,两个盘从X柱转到Z柱一定要3步才行。
然后我再研究了三盘,四盘的情况,发现它们都是由两盘问题构成的,如果描述成公式,就是:
1. 三盘问题=2*两盘问题+1次底盘(第三张盘)移动=2*3+1=7次
2. 四盘问题=2*三盘问题+1次底盘(第四张盘)移动=2*7+1=15次
所以就可以推出代码中的核心部分:
move(n-1, x,z, y);
printf("%c-->%c\n", x, z);
move(n-1, y, x, z);
这三行的具体作用了,对于递归中的最底层调用来说,这三行代码即为两盘问题的三次移动的动作实现,但对于超过两盘问题的三盘问题来说,第一行代码代表第一个两盘问题,而printf函数则是底盘(第三个盘)移动的动作实现,第三行代码则为第二个两盘问题,这也正好证实了前面的”三盘问题=2*两盘问题+1次底盘(第三张盘)移动=2*3+1=7次“,这个公式的合理性 还有一个值得探讨的地方就是两个move函数中X,Y的交换和Y,Z的交换,第一次交换(Y,Z交换)对最底层来说代表两盘排序的第一张盘与第二张盘的移动位置是不同的,其实这里X,Y的交换也起到了分别奇偶的作用,如果有五个盘,那么最底层的参数序列就是X,Y,Z,这样就可以保证底盘(第五张盘)移动时可以到达Z轴,减少移动的总步数以取得最少的步数情况。 这次的代码让我对递归有了新的认识,第一次发现递归其实可以不浪费资源,完美简洁的完成一个看似复杂的问题,算法果然很有趣!