递归入门(汉诺塔问题)
递归
递归 是算法课中最重要的一门必修课,也是我们绕不开的坎,写这篇文章的目的主要是以练代学,一步一步理解递归,形成肌肉记忆
有很多理论性的文章,教你如何理解递归,看完之后还是很懵逼,最初我学递归的时候直接是从入门到放弃,然后打算直接刷题,在题量的累积下,顿悟了
这篇文章我会带着你一边刷题,一边带你理解递归,先让我们来看一个入门级递归问题:汉诺塔问题
汉诺塔问题
概念上的东西不解释太多,简单说一下就是,有三根柱子 A (左),B() , C 。现在要把 a 柱上的盘子全部移到 b 柱上,移动过程中必须遵守一个原则:只有小盘子才能压在大盘子上
因为一次我们只能移动一个盘子,所以需要移动 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 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步