算法学习笔记——洗碗时遇到的汉诺塔问题
前几天去医院体检的时候带了《漫画算法》这本书,在排队的时候看了一些面试中的算法的,再三纠结要不要重复造一回轮子写一些笔记的,然后今天就发现自己快要忘光了......
犹豫就会败北,下次有时间一定会补上。(又是下次一定?)
一、问题描述
下面回到正题上来,今天中午洗碗的时候遇到5个盘子和碗,大小依次递减,于是洗完之后我开始玩起了汉诺塔......
我们看看百度对汉诺塔问题的描述:
如果你不能理解上面的文字:这里我送上一份李永乐老师的讲解视频:
https://www.bilibili.com/video/BV1gJ41177fX?from=search&seid=13257433899054341699
二、什么是递归
看过视频之后,相信大家对于这个游戏怎么玩已经比较清楚了,但是你可能还不清楚什么是递归。
这里我再举一个经典例子:计算n的阶乘。
阶乘的定义式:n!=n*(n-1)*(n-2)*···*3*2*1;
仔细的看一下定义式,聪明的你可能会看出来,我们可以将上述定义式写成如下样子:
n! = n * [ (n-1)! ] ; ①
(n-1)! = (n-1) * [ (n-2)! ] ;② ······依次类推,我们会得到
2! = 2 * [ 1! ] ; ③而
1! = 1 ;④
所以只要我们从【1!】倒回去计算就可以得到【n!】了,这就是递归思想。
三、递归的梳理
如果你理解了递归的思想,你可能就会发现对于递归问题我们需要找到的有:
1)我们先是得到一个递推式(如上面的①式和②式),我们管它叫递归体。
2)根据递推式找到递推的终点(也就是④式),我们管它叫递归头。
3)然后从递归头开始回溯(就是代入到递推式中去),最后得出结果。
这就是我对于递归思想的梳理。
四、上代码
如果你理解上面的例子,那么我们就来动手写一写代码;下面是我给出来的代码:
//递归计算n的阶乘
public class Recursion{
public static void main(String[] args) {
int result1 = fn(3);//6
System.out.println("计算结果是" + result1);
int result2 = fn(5);//120
System.out.println("计算结果是" + result2);
int result3 = fn(10);//3628800
System.out.println("计算结果是" + result3);
//这里无法得到结果是爆栈了,超出int的数据范围,
//fn(100)连long都装不下,可以考虑使用BigInteger处理,这里不做演示
//所以在日常使用中要注意栈溢出问题!!!!!!!
int result4 = fn(100);
System.out.println("计算结果是" + result4);
}
public static int fn(int n){
if(n != 1){//这里就是递归体
return n * fn(n-1);
}else{//这里就是递归头
return 1;
}
}
}
如果上面的没问题了,我们可以回到正题上来了——汉诺塔问题的代码:
(如果不知道怎么写可以返回去再看一遍李永乐老师的思路讲解,然后利用三个盘子的情形来实现)
//汉诺塔问题的代码实现
public class Recursion1 {
public static long count = 0;//定义成一个公共属性,可以计算移动操作的次数
public static void main(String[] args) {
for(int i = 1; i <=5; i++){
System.out.println(i+"个盘子的汉诺塔操作步骤:");
hanoi('a', 'b', 'c', i);
System.out.println("共移动了【" + count + "】次");//结果=2的i次方减1
count = 0;//别忘了置零,不然会累加
System.out.println();
}
System.out.println("检验电脑性能的时候(作死)的时候到了!");
hanoi('a', 'b', 'c', 32);
System.out.println("计算完成");//为了验证你的电脑是不是还在计算中,反正我的电脑算不出来64个盘子
System.out.println("共移动了【" + count + "】次");//4294967295
}
/**
* 将n个盘子从a柱子通过b柱子移动到c柱子上
* @param n 盘子的个数
*/
public static void hanoi(char a,char b,char c,int n){
if(n == 1){//递归头
move(a, c);//将第一个盘子从a直接移动到c
}else{//递归体
hanoi(a,c,b,n-1);//将n-1个盘子从a柱通过c柱移动到b柱
move(a, c);//将第n个盘子从a柱直接移动到c柱
hanoi(b, a, c, n-1);//将n-1个盘子从b柱通过a柱移动到c柱
}
}
public static void move(char a,char c){
//为了单纯得到操作次数所以将输出语句注掉了
//System.out.println("将" + a + "上的盘子移动到" + c);//将最大的盘子从a直接移动到c
count++;//每移动一次,计数加一
}
}
好了,以上就是本期的全部内容了。如果想得到运算时间的同学可以在for循环中利用System.currentTimeMillis()来统计,这里就不做演示了。
感谢大家的查阅,写的不太好,希望收到大家的意见和建议。