前言:汉诺塔问题是源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在第三根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
我们用递归算法来解决这个问题,在解决之前首先介绍一下什么是递归算法。
1、递归算法
在解决一些复杂问题时,为了降低问题的复杂程序,通常是将问题逐层分解,最后归结为一些最简单的问题。这种将问题逐层分解的过程,并没有对问题进行求解,而只是当解决了最后的问题那些最简单的问题后,再沿着原来分解的逆过程逐步进行综合,这就是递归的方法。
一言以蔽之,递归算法的执行过程分递推和回归两个阶段。在递推阶段,把复杂的问题的分解为简单问题,而且必须要有终止递归的情况。在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解。
在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列 “简单问题”层,它们各有自己的参数和局部变量。
2、解决汉诺塔
对于64个金盘,我们分配64个婆罗门,分别编号为1-64;对于金刚柱,编号为A、B、C。当1号婆罗门接到任务后,发现这个很难完成,然后他想,如果有人把前面63个金盘已经移动到了B号柱子,那么我直接将64号盘放在C上即可,然后他移动前63个金盘的任务交给了2号婆罗门,然后他的任务就变成了:
● 让2号婆罗门将前63个金盘移动到B;
● 将第64个金盘移动到C;
● 让2号婆罗门将前63个金盘移动到C;
2号婆罗门也犯了难,他想如果有人把前面62个金盘已经移动到了C号柱子,那么我直接将63号盘放在B上即可,然后他移动前63个金盘的任务交给了3号婆罗门,然后他的任务就变成了:
● 让3号婆罗门将前62个金盘移动到C;
● 将第63个金盘移动到B;
● 让3号婆罗门将前62个金盘移动到B;
3号婆罗门也犯了难,他想如果有人把前面61个金盘已经移动到了B号柱子,那么我直接将63号盘放在C上即可,然后他移动前61个金盘的任务交给了4号婆罗门,就这样递推下去,4号婆罗门又将移动前60个金盘的任务交给了5号婆罗门。。。知道交给64号婆罗门,这时候的任务只需要移动一个金盘了,不需要再去将任务向下分配了。
这时候分配完成,开始移动:64号婆罗门移开第一个金盘,然后63号婆罗门移动第二个金盘;64号婆罗门再将第一个金盘放到第二个金盘的上边。。。
可以看出,只有上一个婆罗门的任务完成了,下一个任务才可能完成,这和递归是一样一样的。
这也相当于,每一个婆罗门都对应一个盘子,例如1号婆罗门对应64号金盘,2号婆罗门对应63号金盘, 每个婆罗门只需要移动自己所管的那个盘子就好了。例如1号婆罗门只需要移动自己管的64号金盘,前面的63个,他会叫别人去移动,至于这63个怎么移,那是别人的事,他只要已经移好的结果;同样2号婆罗门也只管自己对应的63号金盘,至于前边62个金盘怎么移,那是别人的事;同样3号婆罗门。。。。。
可以看出,64个婆罗门都是使用同一个流程的,所以可以用同一个函数表示。而且这个递归是必须有终止条件的,这个条件就是n==1,即任务分配到64号婆罗门,这时候就开始执行任务并向回回溯,分配任务就相当于压栈,执行任务就是弹栈。
3、汉诺塔算法实现
<span style="font-size:18px;"> public void Hanoi(int n, char A, char B, char C)
//第一列为语句行号
{
if (n==1) Move(A, C);
//Move是一个抽象操作,表示将碟子从A移到C上
else {
Hanoi(n-1, A, C, B);
Move(A, C);
Hanoi(n-1, B, A, C);
}
}
</span>
对于我们来说,也只是写出递归代码即可,具体运行是电脑的事,我们不用写出。需要理清的是Hanoi(n)和Hanoi(n-1)之间的参数关系,这是和前面婆罗门的任务相对应的,至于Hanoi(n-1)和Hanoi(n-2)的参数关系同上。也就是说我们只需理清前一步和后一步的关系就行了,这也是递归的含义规定的。
代码很简练,但是需要注意的是,递归算法是一种自身调用自身的算法,随着递归深度的增加,工作栈所需要的空间增大,所以不适用于消耗内存过大的内容,且递归调用时的辅助操作增多,因此,递归算法的运行效率较低。