C语言学习之:递归算法进阶--汉诺塔问题(hanoi)

问题描述

在这里插入图片描述
在这里插入图片描述

问题分析

在这里插入图片描述

如果是初次接触类似的问题,乍看之下肯定会感觉无从下手。

要把64个圆盘从a柱子移动到c柱子上,第一步应该怎么做?虽然可以肯定,第一步唯一的选择是移动 a 最上面的那个圆盘,但是应该将其移到 b 还是 c 呢?很难确定。因为接下来的第二步、第三步……直到最后一步,看起来都是很难确定的。能立即确定的是最后一步:最后一步的盘子肯定也是 a 最上面那个圆盘,并且是由 a 或 b 移动到 c——此前已经将 63 个圆盘移动到了 c 上。

也许你会说,管他呢,先随便试着移动一下好了。如果你这么做,你会发现,接下来你会面临越来越多类似的选择,对每一个选择都“试”一下的话,你会偏离正确的道路越来越远,直到你发现你接下来无法进行为止。

如果将这个问题的盘子数量减为10个或更少,就不会有太大的问题了。但盘子数量为64的话,你一共需要移动约1800亿亿步(18,446,744,073,709,551,615),才能最终完成整个过程。这是一个天文数字,没有人能够在有生之年通过手动的方式来完成它。即使借助于计算机,假设计算机每秒能够移动100万步,那么约需要18万亿秒,即58万年。将计算机的速度再提高1000倍,即每秒10亿步,也需要584年才能够完成。

虽然64个盘子超出了人力和现代计算机的能力,但至少对于计算机来说,这不是一个无法完成的任务,因为与我们人类不同,计算机的能力在不断提高。

  • 对于 64 个盘子的问题有点烧脑,所以我们先来看两个盘子的时候,整个过程很简单:

    • 首先将第一个盘子挪到 B,A–>B
    • 再将第二个盘子挪到 C,A–>C
    • 将第一个盘子挪到 C,B–>C
  • 然后我们再转过头来看 64 个盘子的问题;我们能够发现,如果我们把整个问题划分阶段:

    • 将前 63 个盘子从 A --> B
      在这里插入图片描述
    • 将最后一个盘子从 A–>C
    • 将 63 个盘子从 B–>C

有人可能说了,你只是笼统的这么类比,把前 63 个盘子当做 两个盘子中的第 1 个盘子处理;但是你总觉得不能这么简单的类比,因为这样总有种忽略中间细节、让人不踏实的感觉。没错,你的感觉是对的。初学递归本来就会给你一种不踏实的感觉,你总觉得他与直觉相悖,如果你仍觉得不太放心,我们继续再往下看:

要深入理解递归的原理,就要知道,递归的特点就是:解决的问题的范围越来越小,但是解决的方法在每一层都是一样的,对 2 个数的时候是这样的方法,对 64 个数也是一样的。那你又要说了:“都是一样的不就死循环了?”

是的,这让我想起了一个故事:“从前有座山,山里有座庙,庙里有个老和尚和一个小和尚,老和尚给小和尚讲故事:‘从前有座山,山里有座庙,庙里有个。。。。’” 这确实是死循环,所以他不是递归,真正的递归有出口,什么意思呢?? 意思就是,虽然递归的方法在每一次调用的时候都是一样的;他仍然存在唯一一次例外,那就是递归结束的时候。

在汉诺塔这个例子中,唯一一次不用 A–>B, A–>C, B–>C 的过程,就是只有一个盘子的时候。 这个时候,直接从 A–>C 就可以了。其他的时候,无论多少个盘子,都是从A–>B, A–>C, B–>C 这个过程。

接下来,我们开始关注细节,也就是递归中的具体实现过程,我们可以发现,虽然无论是两个盘子还是 64 个盘子,都是完成的 A–>B, A–>C, B–>C 过程;但是显然在 64 个盘子转移的过程中,A–>B 和 B–>C 这两个过程都涉及到 63 个盘子的转移,而只有A–>C 过程,无论在 2 个盘子的过程还是 64 个盘子的转移过程,都只涉及到 1 个盘子 ,那就是最大的那个盘子。因此,这个步骤是最容易完成的,我们先放在一边。

在 n 个盘子的转移过程中,A–>B 和 B–>C 这两个过程都涉及到 n-1 个盘子的转移;而你有没有发现,一旦转移的盘子数目超过 1, 这就可以看成一个独立的 汉诺塔问题。也就是说, A–>B 你完全可以看成是一个 A 柱上有 n-1 个盘子,B 柱 和 C 柱上没有盘子,最终要把 n-1 个盘子借助 C 柱,全部移动到 B 柱的汉诺塔问题,如下图所示。同样地,B–>C 过程可以看做将 n-1 个盘子从 B柱 借助 A 柱移动到 C柱的单独汉诺塔问题。
因此,我们把 A–>B 的过程和 B–>C 的过程用递归的方式来实现。
在这里插入图片描述

递归代码实现

#include<stdio.h>

void move(char start, char target) {
	printf("%c-->%c\n", start, target);
}
// start 起始柱 A
// process 过程柱(辅助) B
// target 目标柱 C
void hanoi(int n, char start, char process, char target) {

	if (n == 1) {				// 只有一个盘子的时候,递归的出口,A --> C 柱
		move(start, target);
	}
	else
	{			// 这里起始柱还是 A,但是辅助柱变成 C了,目标柱变成 B了
		hanoi(n - 1, start, target, process);  // A-->B 柱用递归来完成,看做单独的汉诺塔问题
		move(start, target);					// A-->C 柱一步到位
		hanoi(n - 1, process, start, target);	// B--C 柱用递归来完成,看做单独的汉诺塔问题
				// 这里起始柱变成 B,辅助柱变成 A,目标柱变成 C
	}
}

void main() {
	hanoi(2, 'A', 'B', 'C');
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暖仔会飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值