算法学习笔记——洗碗时遇到的汉诺塔问题

     前几天去医院体检的时候带了《漫画算法》这本书,在排队的时候看了一些面试中的算法的,再三纠结要不要重复造一回轮子写一些笔记的,然后今天就发现自己快要忘光了......

  犹豫就会败北,下次有时间一定会补上。(又是下次一定?)

一、问题描述

  下面回到正题上来,今天中午洗碗的时候遇到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()来统计,这里就不做演示了。

  感谢大家的查阅,写的不太好,希望收到大家的意见和建议。

原文地址:https://www.cnblogs.com/xzhm/p/12673708.html