盘转乾坤:白话汉诺塔游戏的数字之谜

生椰丝绒:

你好,tatan,欢迎来到我的教室。今天我们要了解一个经典问题,叫做汉诺塔问题。

tatan: 

kakakakakakka,你好!汉诺塔?

听起来好像是一个游戏,是不是要把一些圆盘从一个柱子移到另一个柱子?

生椰丝绒:

没错,你很聪明。汉诺塔问题是这样的,有三根柱子,分别叫做A、B和C,其中A柱子上有n个大小不同的圆盘,从小到大依次叠在一起,如下图所示:

tatan: 

哇,好漂亮的圆盘,有好几种颜色呢。

生椰丝绒:

颜色不重要,重要的是大小。我们的目标是把这些圆盘全部移到C柱子上,但是有一些规则要遵守:

  • 每次只能移动一个圆盘。
  • 移动过程中不能出现大圆盘在小圆盘上方的情况。
  • 可以借助B柱子作为中转。

tatan: 

这听起来好难啊,有没有什么技巧呢?

生椰丝绒:

有的,其实这个问题可以用递归的思想来解决。你知道什么是递归吗?

tatan: 

递归?好像是一个函数调用自己的方法,是吗?

生椰丝绒:

对,递归就是一个函数在执行过程中调用自己,这样可以把一个复杂的问题分解成更小的子问题,直到子问题可以直接解决为止。递归的函数通常有两个部分,一个是基本情况,也就是最简单的情况,可以直接给出答案;另一个是递归情况,也就是把问题转化成更小的问题,然后调用自己来解决。

tatan: 

那汉诺塔问题怎么用递归来解决呢?

生椰丝绒:

我们可以这样想,如果只有一个圆盘,那么我们直接把它从A柱子移到C柱子就可以了,这是基本情况。如果有两个或以上的圆盘,那么我们可以先把除了最大的圆盘之外的所有圆盘从A柱子移到B柱子,这是一个子问题,可以用递归来解决;然后把最大的圆盘从A柱子移到C柱子,这是一个简单的操作;最后把B柱子上的所有圆盘从B柱子移到C柱子,这又是一个子问题,也可以用递归来解决。这就是递归情况。你明白了吗?

tatan: 

我好像有点懂了,就是把一个大问题分成两个小问题,然后再分别解决,是吗?

生椰丝绒:

是的,递归的思考方式就是把大事化小的过程。

用C语言写这个递归函数很简单,我们只需要定义一个函数,叫做hanoi,它有四个参数,分别是n、a、b和c,表示要把n个圆盘从a柱子移到c柱子,借助b柱子作为中转。

然后在函数体中,我们先判断n是否等于1,

如果是,就直接输出把圆盘从a柱子移到c柱子的操作,例如“Move disk 1 from A to C”;

如果不是,就先调用hanoi函数,把n-1个圆盘从a柱子移到b柱子,借助c柱子作为中转,然后输出把最大的圆盘从a柱子移到c柱子的操作,例如“Move disk n from A to C”;

最后再调用hanoi函数,把n-1个圆盘从b柱子移到c柱子,借助a柱子作为中转。

这样就完成了递归函数的定义。你看看这段代码,能看懂吗?

// 定义汉诺塔递归函数
void hanoi(int n, char a, char b, char c) {
  // 如果只有一个圆盘,直接输出操作
  if (n == 1) {
    printf("Move disk 1 from %c to %c\n", a, c);
  } else {
    // 否则,先把n-1个圆盘从a柱子移到b柱子,借助c柱子
    hanoi(n - 1, a, c, b);
    // 然后把最大的圆盘从a柱子移到c柱子
    printf("Move disk %d from %c to %c\n", n, a, c);
    // 最后把n-1个圆盘从b柱子移到c柱子,借助a柱子
    hanoi(n - 1, b, a, c);
  }
}

tatan: 

我觉得我大概能看懂,就是按照刚才说的逻辑来写代码,是吗?

生椰丝绒:

是的,你很棒。那我们来试一下,如果有三个圆盘,我们要把它们从A柱子移到C柱子,我们应该怎么调用这个函数呢?

tatan: 

是不是就是hanoi(3, ‘A’, ‘B’, ‘C’)?

生椰丝绒:

对,你说对了。那我们来看看这个函数会输出什么结果吧:

// 调用汉诺塔递归函数
hanoi(3, 'A', 'B', 'C');
// 输出结果
Move disk 1 from A to C
Move disk 2 from A to B
Move disk 1 from C to B
Move disk 3 from A to C
Move disk 1 from B to A
Move disk 2 from B to C
Move disk 1 from A to C

第一步,把最小的圆盘(编号为1)从A柱子移到C柱子;

第二步,把中等大小的圆盘(编号为2)从A柱子移到B柱子;

第三步,把最小的圆盘(编号为1)从C柱子移到B柱子;

第四步,把最大的圆盘(编号为3)从A柱子移到C柱子;

第五步,把最小的圆盘(编号为1)从B柱子移到A柱子;

第六步,把中等大小的圆盘(编号为2)从B柱子移到C柱子;

第七步,把最小的圆盘(编号为1)从A柱子移到C柱子。

这样,就完成了把三个圆盘从A柱子移到C柱子的操作,你看懂了吗?

tatan: 

哇,好神奇,这样就可以把三个圆盘都移到C柱子上了。那如果有更多的圆盘呢,比如十个圆盘,会不会很复杂呢?

生椰丝绒:

其实不会,我们只需要把3改成10,就可以了,其他的都不用变。你想想,这个函数的逻辑是不是和圆盘的数量无关呢?

tatan: 

是的,好像是这样。

生椰丝绒:

好,我们来看看吧:

// 调用汉诺塔递归函数
hanoi(10, 'A', 'B', 'C');
// 输出结果
Move disk 1 from A to C
Move disk 2 from A to B
Move disk 1 from C to B
Move disk 3 from A to C
Move disk 1 from B to A
Move disk 2 from B to C
Move disk 1 from A to C
Move disk 4 from A to B
Move disk 1 from C to B
Move disk 2 from C to A
Move disk 1 from B to A
Move disk 3 from C to B
Move disk 1 from A to C
......

tatan: 

这样输出也太多了吧,QAQ

生椰丝绒:

是的,这样输出的操作太多了,不方便查看。其实,我们可以不用输出每一步的操作,因为移动方法都是类似的。现在,我们换一下思路,想想怎么用一个公式来计算汉诺塔问题的解法步骤数。你想想,这个公式应该是什么呢?

tatan: 

公式?我不太擅长数学,你能给我一些提示吗?

生椰丝绒:

好的,我来给你一些提示。你看,如果只有一个圆盘,我们只需要一步就可以把它从A柱子移到C柱子,对吗?

tatan: 

对,这很简单。

生椰丝绒:

那如果有两个圆盘呢,我们需要几步呢?

tatan: 

两个圆盘的话,我记得刚才说过,需要三步:

先把小的圆盘从A柱子移到B柱子,

然后把大的圆盘从A柱子移到C柱子,

最后把小的圆盘从B柱子移到C柱子。

生椰丝绒:

你说得对。那如果有三个圆盘呢,我们需要几步呢?

tatan: 

三个圆盘的话,我记得刚才输出的结果,需要七步

生椰丝绒:

你发现了什么规律吗?

tatan: 

规律?我好像发现了,每次增加一个圆盘,就要多两倍的步骤,再加一步,是吗?

生椰丝绒:

是的,你很聪明,这就是汉诺塔问题的公式,用数学语言来表示,就是:

f(n)=2f(n−1)+1

这个公式的意思是,如果有n个圆盘,那么需要的步骤数是前一个圆盘数的两倍,再加一。这个公式是一个递推公式,也就是说,它是用前一个状态来推导出后一个状态的。你看懂了吗?

tatan: 

我觉得我大概能看懂,就是用一个简单的公式来表示复杂的问题,是吗?

生椰丝绒:

是的,你很棒。那我们来试一下,如果有十个圆盘,我们要把它们从A柱子移到C柱子,我们应该怎么用这个公式来计算呢?

tatan: 

我们可以用这个公式来计算,如果有十个圆盘,我们需要的步骤数是:

f(10)=2f(9)+1

但是,我们还不知道f(9)是多少,所以我们还要继续用公式来推导:

f(9)=2f(8)+1

生椰丝绒:

嗯嗯,同理,我们还要继续推导f(8)、f(7)、f(6)、f(5)、f(4)、f(3)、f(2)和f(1),直到我们得到f(1)的值,也就是1。然后,我们就可以从下往上,依次计算出f(2)、f(3)、f(4)、f(5)、f(6)、f(7)、f(8)、f(9)和f(10)的值,如下:

f(1)=1

f(2)=2f(1)+1=2×1+1=3

f(3)=2f(2)+1=2×3+1=7

f(4)=2f(3)+1=2×7+1=15

f(5)=2f(4)+1=2×15+1=31

f(6)=2f(5)+1=2×31+1=63

f(7)=2f(6)+1=2×63+1=127

f(8)=2f(7)+1=2×127+1=255

f(9)=2f(8)+1=2×255+1=511

f(10)=2f(9)+1=2×511+1=1023

所以,如果有十个圆盘,我们需要的步骤数是1023,这是一个很大的数字,你能想象吗?

tatan: 

哇,这么多步骤,我都不敢想象。那如果有一百个圆盘呢,会不会更多呢?

生椰丝绒:

当然会更多,如果有一百个圆盘,我们需要的步骤数是:

f(100)=2f(99)+1

但是,我们不用像刚才那样,一步一步地推导,我们可以用一个更简单的公式来表示,你想想,这个公式应该是什么呢?

tatan: 

我归纳归纳,嗯,好像是

f(n)=2^{^{n}}-1

生椰丝绒:

是的,你很棒。那么总步骤数的问题就迎刃而解了。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值