暴力递归&汉诺塔问题

暴力递归

  1. 将问题转化为规模缩小了的同类问题的子问题。
  2. 有明确的不需要继续递归的条件(base case)
  3. 有当得到了子问题的结果之后的决策过程
  4. 不记录每一个子问题的解

暴力递归的要点大致可以分为以上四条,但是总结起来就是一句话:不断的尝试,所尝即所得

下面用著名的汉诺塔问题来进行带入讲解。

汉诺塔
简单的阐述一下汉诺塔问题:有3根柱子,依次从左到右排列分别是left、mid、right,假设此时左柱上有1、2、3,3个有小到大的圆盘,每次只能挪动一个圆盘,并且小圆盘要永远在大圆盘之上。几步可以将圆盘从left移到right。
在这里插入图片描述
结论:3个圆盘共需要7步

  • 1圆盘left -> right
  • 2圆盘left -> mid
  • 1圆盘right ->mid
  • 3圆盘left -> right
  • 1圆盘mid -> left
  • 2圆盘mid->right
  • 1圆盘left -> right

如何用暴力递归解决
上面的7小步可以统一总结为3大步,分别是:

  1. 上面的2个挪到中间
  2. 最下层最大的挪到右边
  3. 中间的两个挪到右边。

再来确定base case(当什么时候我可以随意挪动):是当圆盘数为1时,如果此时left柱上只剩1个圆盘,那一步就可以直接到right柱。
OK,我们来完善下这部分的代码:

hanoi

  public static void hanoi1(int N) {
        //主过程从最左挪到最右
        leftToRight(N);
    }


  public static void leftToRight(int N) {
       //base case
       //当我只剩1层圆盘时,就可以直接从left -> right
       if (N == 1) {
          System.out.println("Move 1 from left to right");
           return;
       }
       //不止1个圆盘
       //想从 left —> right
       //第一大步: 先将N - 1位置的圆盘,left -> mid
       leftToMid(N - 1);
       //第二大步: 上面挪完,将自己 left -> right
       System.out.println("Move " + N + " from left to right");
       //第三大步 将其他圆盘 mid -> right
       midToRight(N - 1);
  }

上面已经将整体分为了三大步,并且将主流程和每一步对应的操作流程给了出来,下一步,我们再来深入到每一步的流程。

leftToMid
如果我想将圆盘从left -> mid。是不是依然可以分为3大步:

  1. 将圆盘从left -> right
  2. base case 剩我自己时,可以随意挪动
  3. 将其余圆盘从right -> mid。

我们再来完善这部分的细节代码:

public static void leftToMid(int N) {
        if (N == 1) {
            System.out.println("Move 1 from left to mid");
            return;
        }
        leftToRight(N - 1);
        System.out.println("Move " + N + " from left to mid");
        rightToMid(N - 1);
    }

其余主流程剩余的midToRight,是不是也同理。所以完整代码如下:

 public static void hanoi1(int N) {
        leftToRight(N);
    }

    public static void leftToRight(int N) {
        if (N == 1) {
            System.out.println("Move 1 from left to right");
            return;
        }
        leftToMid(N - 1);
        System.out.println("Move " + N + " from left to right");
        midToRight(N - 1);
    }

    public static void leftToMid(int N) {
        if (N == 1) {
            System.out.println("Move 1 from left to mid");
            return;
        }
        leftToRight(N - 1);
        System.out.println("Move " + N + " from left to mid");
        rightToMid(N - 1);
    }

    private static void midToRight(int N) {
        if (N == 1) {
            System.out.println("Move 1 from mid To right");
            return;
        }
        midToLeft(N - 1);
        System.out.println("Move " + N + " from mid to right");
        leftToRight(N - 1);
    }

    private static void midToLeft(int N) {
        if (N == 1) {
            System.out.println("Move 1 from mid to left");
            return;
        }
        midToRight(N - 1);
        System.out.println("Move " + N + " from mid to left");
        rightToLeft(N - 1);
    }

    private static void rightToLeft(int N) {
        if (N == 1) {
            System.out.println("Move 1 from right to left");
            return;
        }

        rightToMid(N - 1);
        System.out.println("Move " + N + " from right to left");
        midToLeft(N - 1);
    }

    private static void rightToMid(int N) {
        if (N == 1) {
            System.out.println("Move 1 from right to mid");
            return;
        }
        rightToLeft(N - 1);
        System.out.println("Move " + N + " from right to mid");
        leftToMid(N - 1);
    }

OK,汉诺塔的问题已经解决了,整体的思路就是先规划好整体的几大步并确定base case,在根据大的步骤完善细节。
但是不知道你们有没有发现,虽然解决了汉诺塔问题,但是代码太过冗余,left -> mid , left -> right , mid -> right , mid -> left,每一个都对应着两个方法。
那我们不妨将问题稍微抽象化一点,加一些参数,来简化这个方法。
抛去left,mid和right的概念,我们此时只有from to 和other,目的是将圆盘整体从from挪到to,我们依然分为3步来看:

  1. base case依然是当圆盘数为 1 时, 可以随意挪动。 这点无可厚非。
  2. 第一步: 想要圆盘数为 1 的话,还是要将N - 1的圆盘全部挪到其他圆盘上,但是此时,圆盘都在from,应该是从from -> to, to是谁? to应该是上面提到的other吧,只有将N - 1圆盘从 from 挪到了other上,最后一个圆盘才可以从from -> to。
  3. 第二步:此时,from上只剩一个圆盘,可以直接挪动从from -> to
  4. 第三步:是不是这一步再将N - 1挪到to上,整个汉诺塔问题就完成了。 怎么挪 ? 第一步将 N - 1从from挪到other,此时other是不是就是那个 from,to不变,from变成了other的概念。

将问题稍微抽象一些,并且增加了几个参数,此时再来看一看优化后的代码:
hanoi2

 public static void hanoi2(int N) {
        func(N, "left", "right", "mid");
    }

    public static void func(int N, String from, String to, String other) {
        if (N == 1) {
            System.out.println("Move 1 from " + from + " to " + to);
        } else {
            func(N - 1, from, other, to);
            System.out.println("Move " + N + " from " + from + " to " + to);
            func(N - 1, other, to, from);
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值