大聪明教你学Java | 深入浅出聊递归(以汉诺塔为例)

10,235 阅读7分钟

前言

不知道各位小伙伴是什么时候接触到了递归,记得我第一次学习递归的时候是通过“汉诺塔”的例子来学习的,“汉诺塔”的代码虽然没几行,却也困扰了我很久(脑子太笨了,转不过来😂),经过了很长一段时间以后才算是理解了其中的逻辑,今天就和大家深入浅出的聊聊什么是递归,分享一下我学习递归的一些经验和心得。

汉诺塔

汉诺塔(又称河内塔)源于印度的一个古老传说:大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

后来这个传说就演变成了现在的汉诺塔游戏,这个游戏的规则也很简单👇

  1. 有三根杆子A、B、C,在 A 杆上有若干个圆盘
  2. 每次只能移动一个圆盘,并且小的只能叠在大的上面
  3. 将所有的圆盘从 A 柱子挪到 C 柱子就算成功

在这里插入图片描述 知道了游戏规则,接下来我们该思考一下如何完成游戏的最终任务呢~ 俗话说“繁琐问题必有猥琐解法”:我有劲💪,直接把圆盘全拿起来放到 C 柱子上就搞定了,区区几个小圆盘还是能拿得动的😂 这个办法确实够“猥琐”,也够简单粗暴,但仅仅是跟大家开个小玩笑,下面我们还是言归正传,正经的分析一下这个游戏的奥秘👇

当 A 柱上仅仅只有1个圆盘时,那么直接将圆盘从 A 柱上移动到 C 石柱上即可。

当 A 柱上有2个圆盘时,把 A 柱上的圆盘从上往下按照大小顺序编为1号和2号,那么要将圆盘全部从 A 柱移动到 C 柱,首先需要将1号圆盘移动到 B 柱,再将2号圆盘从 A 柱移动到 C 柱,最后将1号圆盘从 B 柱移动到 C 柱。

当 A 柱上有3个圆盘时,仍然从上往下按照大小顺序将圆盘编为1号、2号和3号,这时候我们就发现问题变得复杂了一些,所以我们可以将1号和2号圆盘看做一个整体(即“1-2号”圆盘),此时我们需要解决的就是将“1-2号”圆盘和3号圆盘移动到 C 柱的问题,也就是需要先将“1-2号”圆盘移动到 B 柱,再将3号圆盘移动到 C 柱,最后将“1-2号”圆盘移动到 C 柱即可。

以此类推,如果当 A 柱上有 N 个圆盘的时候,我们依然使用“看作整体”的办法来解决问题,我们依然是将 A 柱上的圆盘从上往下按照大小顺序编为1号、2号、3号...N号,我们此时就需要将1号到 N-1 号圆盘看做一个整体(记作“1→N-1”号圆盘),这时候我们就需要先将“1→N-1”号圆盘移动到 B 柱上,再将N号圆盘移动到 C 柱上,最后再把 B 柱上的“1→N-1”号圆盘移动到 C 柱上。我们可以发现当N号圆盘移动到 C 柱后,它就不需要再次移动了,也就是说无论其他的圆盘怎么移动都不需要N号圆盘的参与了,这也就代表了我们看作的整体(“1→N-1”号圆盘)和单独的个体(N号圆盘)相互独立且互不影响,所以我们就可以把“1→N-1”号圆盘按照上面的逻辑继续拆分,最终即可将所有的圆盘从 A 柱移动到 C 柱上。

我们可以对上面的分析做一个总结👇

  1. 第一种情况就是只有一个圆盘的时候,我们只需要操作一步,直接将圆盘从 A 柱移动到 C 柱即可
  2. 当有N个圆盘时,我们的操作过程就分成了三步:①将前N-1个圆盘从 A 柱移动到 B 柱 ②将N号圆盘从 A 柱移动到 C 柱 ③将前N-1个圆盘从 B 柱移动到 C 柱

我们有了上面的总结,就可以写出一个移动汉诺塔的代码👇

/**
 * 汉诺塔
 * @description: HanoiDemo
 * @author: 庄霸.liziye
 * @create: 2022-01-11 10:18
 **/
public class HanoiDemo {
    public void hanoi(int n, String A, String B, String C) {
        if (n == 1) {
            //只有一个圆盘,直接将圆盘从A柱移动到 C柱
            move(n, A, C);
        } else {
            //借助C柱将前N-1个圆盘从A柱移动到B柱
            hanoi(n - 1, A, C, B);
            //将N号圆盘从 A 柱移动到 C 柱
            move(n, A, C);
            //借助A柱将前N-1个圆盘从 B 柱移动到 C 柱
            hanoi(n - 1, B, A, C);
        }
    }

    /**
     * 移动N号圆盘
     **/
    private void move(int n, String A, String C) {
        System.out.println("将"+ n +"号圆盘从" + A + "移动到" + C);
    }

    public static void main(String[] args) {
        HanoiDemo hanoiDemo = new HanoiDemo();
        System.out.println("A柱上有3个圆盘时移动的步骤:");
        hanoiDemo.hanoi(3, "A柱子", "B柱子", "C柱子");
    }
}

递归

通过代码可以看出来,我们利用递归的思想成功了解决了汉诺塔的问题,其实递归的思想也很简单,我们只需要注意两点就行:结束条件和让问题规模变小。在汉诺塔的例子中,将所有圆盘从 A 柱移动到 C 柱就是结束条件,我们使用到的“看作整体”的方式就是让问题的规模变小。可能很多人都觉得在学习的过程中要做到“知其然知其所以然”,但是在学习递归的时候却恰恰相反,我们只需要做到“不求甚解”就可以了。学习递归的“猥琐”办法就是舍弃,说的具体一点就是要舍弃掉想理解和跟踪递归全程的想法,否则会让自己陷入到一个死循环里,不仅无法理解递归,还会让自己懵逼。

回归到汉诺塔的例子中,如果此时有人让我们去移动“1→N-1”号圆盘怎么办?我们可能会很头疼,有N-1个圆盘需要我们去移动,这不就把我们累坏了😭,其实解决问题的办法很简单,我们也可以学给我们分配任务的那个人,把工作外包出去,也就是说我们只管N-1号圆盘,其他的“1→N-2”号圆盘都外包给别人,这下我们就成了一个包工头,这回我们是不是就轻松了许多😄。

这时候我们就千万别再管接到外包工作的家伙有多头疼了,丢给他就让他难受去,千万别再折磨自己了。 接到外包工作的人愿意怎么实现就怎么实现去,跟我们没有鸡毛关系,我们只需要关注自己的那个圆盘就好了。或许接到我们的外包工作的人也想省点心,就把“1→N-3”号圆盘外包出去了,自己只管N-2号圆盘...

这最终就成了一个任务链:大包工头→包工头→小包工头→小小包工头→小小小包工头.... 当最后一个超小的包工头完成了自己的任务,那么在他之上的各位包工头也就很容易的完成了自己所负责的那部分任务,在各位包工头的共同努力之下,整个任务就被完美的完成啦✌(是不是有点像一句俗话:大懒支小懒🤭)。

小结

本人经验有限,有些地方可能讲的没有特别到位,如果您在阅读的时候想到了什么问题,欢迎在评论区留言,我们后续再一一探讨🙇‍

希望各位小伙伴动动自己可爱的小手,来一波点赞+关注 (✿◡‿◡) 让更多小伙伴看到这篇文章~ 蟹蟹呦(●'◡'●)

如果文章中有错误,欢迎大家留言指正;若您有更好、更独到的理解,欢迎您在留言区留下您的宝贵想法。

爱你所爱 行你所行 听从你心 无问东西