System.o|

fengzeng

园龄:4年9个月粉丝:17关注:5

递归入门(汉诺塔问题)

递归

递归 是算法课中最重要的一门必修课,也是我们绕不开的坎,写这篇文章的目的主要是以练代学,一步一步理解递归,形成肌肉记忆

有很多理论性的文章,教你如何理解递归,看完之后还是很懵逼,最初我学递归的时候直接是从入门到放弃,然后打算直接刷题,在题量的累积下,顿悟了

这篇文章我会带着你一边刷题,一边带你理解递归,先让我们来看一个入门级递归问题:汉诺塔问题

汉诺塔问题

概念上的东西不解释太多,简单说一下就是,有三根柱子 A (左),B() , C 。现在要把 a 柱上的盘子全部移到 b 柱上,移动过程中必须遵守一个原则:只有小盘子才能压在大盘子上

image-20220220202646283

因为一次我们只能移动一个盘子,所以需要移动 7 次才可解决问题,即:

  1. 把 小盘 从 左 移到 右

    image-20220220202851951

  2. 把 中盘 从 左 移动 中

    image-20220220202932002

  3. 把 小盘 从 右 移到 左

    image-20220220203009112

  4. 最大的盘子 从 左 移到 右

    image-20220220203059822

  5. 把 小盘 从 **中 -> 左 **

    image-20220220203203188

  6. 中盘 从 中 -> 右

    image-20220220203249220

  7. 最后一步 :小盘 从 左 -> 右

过程很简单,因为一次只能移动最顶上的盘子,所以我们要移动大盘子,就要把它上面的小盘子全部移走才行

我们来看一下 coding

public static void hanoi(int n) {
  leftToRight(n);
}
  private static void leftToRight(int n) {
    // 如果只有一个盘子,直接移即可
    if (n == 1) {
      System.out.println("Move 1 from left to right");
      return;
    }
    // 把 n-1 层盘子从左移到中,
    // 再把最底下的盘子移到右,再把中间的 n-1 层盘子移到右 
    leftToMid(n - 1);
    System.out.println("Move " + n + " from left to right");
    midToRight(n - 1);
  }

是不是很简单

我们再看一下 leftToMid 方法怎么写

还是和之前一样分析, 我们要把 n 层盘子从左移到中,就需要先把 n-1 个盘子从左挪到右,然后把左边最底下的盘子挪到中,然后再把 n-1 层盘子从右边挪到中间

private static void leftToMid(int n) {
  // 如果只有一层,直接挪即可
  if (n == 1) {
    System.out.println("Move 1 from left to mid");
    return;
  }
  // 要把 n 层盘子从左挪到中,需要先把 n-1 层挪出去给第 n 层腾位置
  // 所以把 n-1 层从左挪到右
  leftToRight(n - 1);
  // 再把第 n 层挪到中
  System.out.println("Move " + n + " from left to mid");
  // 再把挪到右边的 n-1 层挪回来
  rightToMid(n - 1);
}

完整代码如下:

public static void hanoi(int n) {
  leftToRight(n);
}
/**
 * 把 n 层圆盘,从左 -> 右
 * @param n
 */
private static void leftToRight(int n) {
  // 如果只有一个盘子,直接移即可
  if (n == 1) {
    System.out.println("Move 1 from left to right");
    return;
  }
  // 把 n-1 层盘子从左移到中,
  // 再把最底下的盘子移到右,再把中间的 n-1 层盘子移到右
  leftToMid(n - 1);
  System.out.println("Move " + n + " from left to right");
  midToRight(n - 1);
}



private static void leftToMid(int n) {
  // 如果只有一层,直接挪即可
  if (n == 1) {
    System.out.println("Move 1 from left to mid");
    return;
  }
  // 要把 n 层盘子从左挪到中,需要先把 n-1 层挪出去给第 n 层腾位置
  // 所以把 n-1 层从左挪到右
  leftToRight(n - 1);
  // 再把第 n 层挪到中
  System.out.println("Move " + n + " from left to mid");
  // 再把挪到右边的 n-1 层挪回来
  rightToMid(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);
}

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 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 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);
}

这就是傻白甜的汉诺塔问题

这个过程我们可以优化一下,我们先忘掉左中右,假如我们现在还是有三个柱子:from,to,other

我们需要把 n 层盘子从 from 挪到 to 去,首先我们要把 n-1 层盘子挪到 other 上,然后把 n 从 from 挪到 to, 再把 other 上的 n-1 层盘子挪到 to,other 作为腾位置的柱子。

优化后的代码:

public static void hanoi2(int n) {
  if (n > 0) {
    func(n, "left", "right", "mid");
  }
}

  public static void func(int n, String from, String to, String other) {
    // base case: 如果只有一个盘子,直接移即可
    if (n == 1) {
      System.out.println("Move 1 from " + from + " to " + to);
      return;
    }

    // 把 n-1 层从 from 挪到 other 上,to 充当腾位置的柱子
    func(n - 1, from, other, to);
    System.out.println("Move " + n + " from " + from + " to " + to);
    // 把 n-1 层从 other 挪到 to ,from 充当腾位置的柱子
    func(n - 1, other, to, from);
  }

这里我们学到一个技巧:一个递归函数,我们可以增加参数的方式来表达更多的可能性

这句话的意思就是:我们可以在递归函数中添加参数去实现更多的功能

这是我写递归入门的第一篇文章,后面会继续连载包括 记忆化递归,递归到动态规划等

我最开始学递归的时候,也觉得递归是一门玄学,不过这都不重要

重要的是我们需要有黑盒思维,什么是黑盒思维?

就是我们不用关注这个 api 是怎么实现的,我们只需要知道他能返回给我们需要的结果即可

本文作者:fengzeng

本文链接:https://www.cnblogs.com/Fzeng/p/15916589.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   fengzeng  阅读(59)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起