汉诺塔问题古老的历史,我们在实际中就不去纠结了,直接看大致描述:有三个柱子,分别为A,B,C,在A柱子上,有n个从大到小堆在一起的盘子,现在要借助B柱子,把A上面的盘子,全部移动到C柱子上,移动的过程中,需要保证大盘子在下面,小盘子在大盘子的上面。类似这样的效果:
汉诺塔问题的求解是需要借助于递归方法来实现的。
这里假设有一个盘子,那么直接就是从A移动到C柱子即可。A->C
如果有两个盘子,那么需要先将小盘子移动到B柱子上,接着将大盘子从A移动到C柱子,然后移动B上的盘子到C上。A->B A->C B->C。
如果有三个盘子,那么就稍微复杂一点了。需要考虑将两个小的盘子移动到B上面,然后将最大的盘子从A上面移动到C上面,最后再来将两个B上面的盘子移动到C上面。到了这里,基本的递归思路可以产生了:
1、就是我们不管前面有多少个盘子,就是需要将A上面除了最大的盘子之外的所有n-1个盘子借助C移动到B。
2、然后移动A柱子上最大的盘子到C柱子(A->C),这时候,就无需再考虑最大盘子的移动了,就是剩下的n-1个盘子,怎么把他们从B移动到C上面。
3、我们需要借助的柱子变成了A,因为A上面没有盘子了,问题变成了B柱子借助A柱子,将n-1个盘子移动到C柱子。
伪代码:
hanoi(n,A,B,C){
hanoi(n-1,A,C,B); // n-1 个小盘子借助C移动到B上,这时候A上就剩下最大的盘子,而C上没有盘子
move(A->C); // 直接移动A柱子上的大盘子到C上
hanoi(n-1,B,A,C); // n-1个盘子从B借助A柱子移动到C上
}
这里的代码,不同于我们对一般递归的理解,一般的递归,比如计算n的阶乘,除了终止条件之外,在函数体内部,我们只需要调用一次递归函数,传入n-1参数。
我们这里解决汉诺塔问题,显得有些难以理解,为什么是调用两次,而且是在两次调用中间加上移动操作,一般而言,移动操作就是一个打印语句,在java语言中就是System.out.println(from+"->"+to) ,在javascript语言中就是console.log(from+"->"+to)。如下所示,javascript实现的递归方法解决汉诺塔问题的代码:
另外,这里使用其他语言来实现的话,代码都大同小异,如下是java实现的方式:
package com.xxx.javaee.algrithm;
public class HanoiPower {
/**
*
* @param n
* @param from
* @param temp
* @param to
*/
public static void hanoi(int n,char from,char temp,char to) {
if(n==1) {
System.out.println(from+" -> "+to);
return;
}
hanoi(n-1, from, to, temp);
System.out.println(from +" -> "+to);
hanoi(n-1, temp, from, to);
}
public static void main(String[] args) {
hanoi(3, 'A','B','C');
}
}
运行结果也都是一样的:
个人对于汉诺塔问题的理解,通过代码能够反向理解这个操作的意义,但是在没有代码的情况下,很难从原理上理解代码。唯一能够理解的是,这个问题通过递归来解决相对容易。