汉诺塔精解

如何理解汉诺塔?如何使用递归编写代码解决汉诺塔问题?

不可否认,递归算法解汉诺塔问题看起来很抽象,很多人都难以理解,甚至困扰几个月都难以明白。

现在,我一步一步来实现用C语言解决这个汉诺塔问题,如果你有兴趣的话,也许对你理解递归又有更深层次的认识。

#include <stdio.h>

int main()
{
	int num = 0;		/* 要移动的盘子数 */
	printf("请输入要移动的盘子的个数:>");
	scanf("%d", &num);
	return 0;
}

这段代码是最基本的,现在还没有开始构建函数。我们定义了三个变量src, mid, des, 分别代表三个柱子。

我们的目的是把 src 上的 n 个盘子都移动到 des 上。还要满足每次只能移动一个盘子且小盘子必须在大盘子上的条件。

那么我们是不是可以这样想:要从 src 上移动 n 个盘子到 des 上,是不是相当于从 src 上移动 n-1 个盘子到 mid 上,再把 src 上剩下的一个盘子移动到 des 上,最后把 mid 上的 n-1 个盘子移动到 des 上。而对于如何把 n-1 个盘子移动到 mid 上,我们又可以重复这样看。

#include <stdio.h>

void Hanio(int n, char src, char mid, char des);

int main()
{
	int num = 0;		/* 要移动的盘子数 */
	printf("请输入要移动的盘子的个数:>");
	scanf("%d", &num);
	Hanio(num, 'A', 'B', 'C'); 

	return 0;
}

void Hanio(int n, char src, char mid, char des) /* src,mid,des分别表示起始柱,中间柱和终点柱 */
{
	//把 n-1 个盘子从 src 通过 des 移动到 mid
	
	//把第 n 个盘子从 src 直接移动到 des

	//把第 n-1 个盘子从 mid 通过 src 移动到 des
}

这就是我们的思路,再继续编写一个Move()函数,来实现一个盘子从一个柱子移动到另一个柱子。

#include <stdio.h>

void Hanio(int n, char src, char mid, char des);
void Move(int n, char src, char des);

int main()
{
	int num = 0;		/* 要移动的盘子数 */
	printf("请输入要移动的盘子的个数:>");
	scanf("%d", &num);
	Hanio(num, 'A', 'B', 'C');
	return 0;
}

void Hanio(int n, char src, char mid, char des) /* src,mid,des分别表示起始柱,中间柱和终点柱 */
{
	//把 n-1 个盘子从 src 通过 des 移动到 mid
	
	//把第 n 个盘子从 src 直接移动到 des
	Move(n, src, des);
	//把第 n-1 个盘子从 mid 通过 src 移动到 des
}
 
void Move(int n, char src, char des)
{
	printf("Number %d, from %c to %c\n", n, src, des);
}

由于第二个步骤很简单,只有一个盘子需要移动,所以我们直接调用Move()函数。关键就在于第一个步骤和第三个步骤。

我们的目的就是要将 n 个盘子从 src 移动到 des,所以我们才构建了Hanio()函数,记住了,这个Hanio()函数的功能就是将 n 个盘子从第一个柱子移动到第三个柱子。

而现在呢?我们第一步是实现把 n-1 个盘子从 src 通过 des 移动到 mid,转换一想,这不就是我们函数的功能吗?第一个柱子上的盘子移动到第三个柱子上的盘子,而这里是把第一个柱子上的盘子移动到第二个柱子上的盘子,所以只要把 n 换成 n-1,mid和des互换,即:

Hanio(n - 1, src, des, mid);

这个函数通用于剩下的盘子。所以可递归下去,直到限制条件。 

也许你觉得不可思议,我们现在就是要实现Hanoi()函数的功能啊,怎么函数实现的第一个步骤还要调用Hanio()的功能呢?而现在Hanio()的功能还没有啊,正在写第一个步骤实现呢……套娃?无线循环?

其实不是,这正是递归的精髓。我们现在只是设想Hanio()函数的功能已经完善了,现在第一个步骤呢只是调用它的功能,这个时候第一个步骤是建立在Hanio()函数完成的基础上,所以只有把整个函数编写完,第一个步骤才算完成了。

注意:很多人都认为完成第一个步骤之后我们就已经把 src 上的 n-1 个盘子移动到了 mid 上,接着第二个步骤的函数将最后一个盘子移动到 des 上,第三个步骤就把 n-1 个盘子移动到 des 上。No,  No. 它们并不是独立的,如果只写第一个步骤,直接去运行,你会发现并没有把 n-1 个盘子移动到中间柱子上,而是只发生了一次移动!为什么呢?因为Hanio()的功能还没有完善,所以第一个步骤也就不可能成功。因为第一个步骤是调用Hanio()函数,所以要基于Hanio()的功能。

那么第三个步骤也是同样的道理,运用Hanio()函数本身的功能,将剩下的 n - 1 个盘子从第二个柱子移动到第三个柱子。

Hanio(n - 1, mid, src, des);

#include <stdio.h>

void Hanio(int n, char src, char mid, char des);
void Move(int n, char src, char des);

int main()
{
	int num = 0;		/* 要移动的盘子数 */
	printf("请输入要移动的盘子的个数:>");
	scanf("%d", &num);
	Hanio(num, 'A', 'B', 'C');
	return 0;
}

void Hanio(int n, char src, char mid, char des) /* src,mid,des分别表示起始柱,中间柱和终点柱 */
{

	//把 n-1 个盘子从 src 通过 des 移动到 mid
	Hanio(n - 1, src, des, mid);
	//把第 n 个盘子从 src 直接移动到 des
	Move(n, src, des);
	//把第 n-1 个盘子从 mid 通过 src 移动到 des
	Hanio(n - 1, mid, src, des);
}

void Move(int n, char src, char des)
{
	printf("Number %d, from %c to %c\n", n, src, des);
}

还剩下一个限制条件,否则函数将无限递归下去。

当 n == 1 时,即一个盘子,我们可以将其直接从第一个柱子移动到第三个柱子。代码如下:

#include <stdio.h>

void Hanio(int n, char src, char mid, char des);
void Move(int n, char src, char des);

int main()
{
	int num = 0;		/* 要移动的盘子数 */
	printf("请输入要移动的盘子的个数:>");
	scanf("%d", &num);
	Hanio(num, 'A', 'B', 'C');
	return 0;
}

void Hanio(int n, char src, char mid, char des) /* src,mid,des分别表示起始柱,中间柱和终点柱 */
{
	if (1 == n)
	{
		Move(n, src, des);
	}
	else
	{
		//把 n-1 个盘子从 src 通过 des 移动到 mid
		Hanio(n - 1, src, des, mid);
		//把第 n 个盘子从 src 直接移动到 des
		Move(n, src, des);
		//把第 n-1 个盘子从 mid 通过 src 移动到 des
		Hanio(n - 1, mid, src, des);
	}
}

void Move(int n, char src, char des)
{
	printf("Number %d, from %c to %c\n", n, src, des);
}

原来这就是递归,比如第一个步骤和第二个步骤,其实并不是独立完成一项功能,而是在调用函数本身的功能,相互交替完成的。

补充一个,其实Hanio(n-1, src, des, mid)函数在不断地调换第二个和第三个柱子,Hanio(n-1, mid, src, des)函数在不断地调换第一个和第二个柱子。这是微观角度,但是这个是递归,所以我们从宏观角度来看,第一个步骤和第三个步骤都在调用Hanio()函数本身的功能,所以说用递归能方便我们理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值