【汉诺塔问题】#C语言 #函数递归 #保姆级思路

大家好,很高兴又和各位见面啦!今天博主将给大家详细介绍一个非常经典的问题——汉诺塔问题。

编写函数来解决汉诺塔问题:

(1)什么是汉诺塔?

简单的理解就是有三根柱子,其中一根柱子上有n个由上到下逐渐增大的圆盘,我们需要在保证圆盘始终是大圆盘在下,小圆盘在上的情况下每次移动一个圆盘,直到完成所有圆盘的移动。如图所示:

我们需要在保证圆盘按照下面大上面小的顺序叠放的情况下将圆盘转移到其它柱子上。

(2)解题思路

移动流程

假设在A柱上有n个圆盘,我需要将圆盘从A柱转移到B柱,那我就需要按以下步骤完成转移:

将n-1个圆盘先转移到C柱,并按顺序叠放;

将第n个圆盘移动到B柱;

将n-2个圆盘再转移到A柱,并按顺序叠放;

将第n-1个圆盘移动到B柱;

不断重复前面四个步骤,每次需要转移的圆盘数量减1;

简化问题

为了将这个问题简单化,我们始终将圆盘看成1/2/3三个部分,然后进行移动;

第一步将1移动到B柱;

第二步将2移动到C柱;

第三步将1移动到C柱;

第四步将3移动到B柱;

第五步将1移动到A柱;

第六步将2移动到B柱;

第七步将1移动到B柱;

不断重复上述步骤,直到所有圆盘完成叠放。

功能实现

功能一——移动圆盘

为了向大家展示移动过程,我们可以通过打印函数来表示圆盘的移动;

功能二——重复移动

需要实现重复移动的功能,首先我们就需要找出移动的共同点,这里我们先将圆盘看成两部分第n个圆盘和n-1个圆盘:

第一步,将n-1个圆盘从A柱移动到C柱;

第二步,将第n个圆盘从A柱移动到B柱;

第三步,将n-1个圆盘从C柱移动到B柱;

因此我们可以通过递归来重复上述操作:

//功能一——移动圆盘
void move(char A, char B, int n)
{
	//通过打印来展示圆盘的移动过程
	printf("第%d个圆盘从%c移动到%c\n", n , A, B);
}
//功能二——重复移动圆盘
void HNT(char A, char B, char C, int n)
{
	if (1 == n)//当圆盘只有1个时
	{
		//圆盘将从A柱移动到B柱
		move(A, B, n);
	}
	else//当圆盘不止一个时
	{//通过递归来实现圆盘移动的重复操作
		//n-1个圆盘将从A柱移动到C柱
		HNT(A, C, B, n - 1);
		//第n个圆盘将从A柱移动到B柱
		move(A,B, n );
		//第n-1个圆盘将从C柱移动到B柱
		HNT(C, B, A, n - 1);
	}
}

如图所示,第一步我们需要借助B柱将n-1个圆盘全部从A柱移动到C柱,我们需要将参数A、C、n-1传给函数move以此将过程打印出来,即​​move(A,C,n-1)​​​,在代码中我们通过递归HNT函数来完成传参,即​​HNT(A,C,B,n-1)​​;

第二步,此时圆盘只有1个也就是此时n=1,我们将圆盘从A柱直接移动到B柱,此时我们需要将A、B、n传给函数move将过程打印出来,即​​move(A,B,n)​​;

第三步,我们需要将n-1个圆盘借助A柱从C柱移动到B柱,此时我们需要将C、B、n-1传给函数move以此将移动过程打印出来,即​​move(C,B,n-1)​​​,在代码中我们通过递归完成传参,即​​HNT(C,B,A,n-1)​​;

代码解析

这里假设我们输入值为3,在进入HNT函数后,代码的运行流程如下所示:

  1. 此时HNT函数的形参为A='A',B='B',C='C',n=3;

  1. 判断​​3 != 1​​,执行第一个递进HNT函数的第一层递进HNT函数,此时的HNT函数的形参为A=A='A',B=C='C',C=B='B',n-1=2;

  1. 判断​​2 != 1​​,执行第一层递进HNT函数的第二层递进HNT函数,此时的HNT函数的形参为A=A=A='A',B=C=B='B',C=B=C='C',n-2=1;

  1. 判断​​1 == 1,​​执行嵌套函数move进行打印,此时的move函数形参为A=A='A',B=B='B',n-2=1;

  1. 打印内容为第1个圆盘从A移动到B,此时第二层递进HNT函数已经执行完,开始进行回归
  2. 回归第一层递进HNT函数,顺序执行第一层递进HNT函数的嵌套函数move进行打印,此时的move函数形参为A=A='A',B=B='C',n-1=2;

  1. 打印内容为第2个圆盘从A移动到C
  2. 回到第一层递进HNT函数,顺序执行第一层递进HNT函数的第二个递进HNT函数,此时的HNT函数的形参为:A=C=B='B',B=B=C='C',C=A=A='A',n-2=1;

  1. 判断​​1 == 1,​​执行嵌套函数move进行打印,此时的move函数的形参为A=A='B',B=B='C',n-2=1;

  1. 打印内容为第1个圆盘从B移动到C
  2. 回到第一层递进HNT函数,此时第一层递进HNT函数已经执行完,开始进行回归;

  1. 回归到HNT函数,顺序执行HNT函数后续嵌套函数move,此时的move函数形参为A=A='A',B=B='B',n=3;

  1. 打印内容为第3个圆盘从A移动到B;
  2. 回到HNT函数,顺序执行HNT函数的第二个递进HNT函数的第一层递进,此时的HNT函数的形参为A=C='C',B=B='B',C=A='A',n-1=2;

  1. 判断​​2 != 1​​,执行第二层递进HNT函数,此时的HNT函数的形参为A=A=C='C',B=C=A='A',C=B=B='B',n-2=1;

  1. 判断​​1 == 1,​​执行嵌套函数move进行打印,此时的move函数的形参为A=A='C',B=B='A',n-2=1;

  1. 打印内容为第1个圆盘从C移动到A,此时第二层递进HNT函数已经执行完,开始进行回归
  2. 回归第一层递进HNT函数,顺序执行第一层递进HNT函数的嵌套函数move进行打印,此时的move函数形参为A=A='C',B=B='B',n-1=2;

  1. 打印内容为第2个圆盘从C移动到B
  2. 回到第一层递进HNT函数,顺序执行第一层递进HNT函数的第二个递进HNT函数,此时的HNT函数的形参为:A=C=A='A',B=B=B='B',C=A=C='C',n-2=1;

  1. 判断​​1 == 1,​​执行嵌套函数move进行打印,此时的move函数的形参为A=A='A',B=B='B',n-2=1;

  1. 打印内容为第1个圆盘从A移动到B此时第一层递进HNT函数已经执行完,开始进行回归

  1. 回归到HNT函数,此时HNT函数全部执行完成,回到主函数,结束程序运行。

代码理解的重难点在于参数的变化,大家可以参照流程图解以及文字解释来进一步理解汉诺塔函数通过递进的实现圆盘的重复移动。

功能三——计算次数

计算汉诺塔移动的次数的方式有很多,比如可以在move函数中加入计数变量,也可以通过公式来进行计算,这里我介绍一下通过公式进行计算移动次数。

首先我们先将移动的方式展示出来:

第一步,将n-1个圆盘从A柱移动到C柱,会移动f(n-1)步;

第二步,将第n个圆盘从A柱移动到B柱,会移动1步;

第三步,将n-1个圆盘从C柱移动到B柱,会移动f(n-1)步;

也就是说在将n个圆盘从A柱移动到B柱,总共需要移动f(n)=f(n-1)+1+f(n-1)=2*f(n-1)=1步,这也就是我们的递推公式

汉诺塔移动次数的公式我们可以通过数学归纳法来求解;

圆盘数量(n)

移动次数f(n)

递推公式

1

1

1

2

3

2*f(1)+1

3

7

2*f(2)+1

4

15

2*f(3)+1

5

31

2*f(4)+1

……

n

2^n-1

2*f(n-1)+1

通过数学归纳法不难证明,因为第1个圆盘的移动次数为2^1-1,假设第n个圆盘的移动次数为2^n-1成立,那我只需要证明第n+1个圆盘的移动次数为2^(n+1)-1成立即可,;

根据递推公式可得,第n+1个圆盘的移动次数为f(n+1)=2*f(n)+1=2*(2^n-1)+1=2^(n+1)-1,证明完毕。

所以n个圆盘的移动次数公式为:f(n)=2^n-1

由此我们可以尝试编写代码来求解圆盘的移动次数:

//功能三——计算次数
int num(int x)
{
	int n = pow(2, x) - 1;
	return n;
}

(3)汉诺塔问题的实现

通过前面的解题分析,我们已经将汉诺塔所需的三个功能的代码全部编写完毕,下面只需要将这三个功能整合起来,就可以了:

//功能一——移动圆盘
void move(char A, char B, int n)
{
	//通过打印来展示圆盘的移动过程
	printf("第%d个圆盘从%c移动到%c\n", n , A, B);
}

//功能二——重复移动圆盘
void HNT(char A, char B, char C, int n)
{
	if (1 == n)//当圆盘只有1个时
	{
		//圆盘将从A柱移动到B柱
		move(A, B, n);
	}
	else//当圆盘不止一个时
	{//通过递归来实现圆盘移动的重复操作
		//n-1个圆盘将从A柱移动到C柱
		HNT(A, C, B, n - 1);
		//第n个圆盘将从A柱移动到B柱
		move(A, B, n);
		//第n-1个圆盘将从C柱移动到B柱
		HNT(C, B, A, n - 1);
	}
}
//功能三——计算次数
int num(int x)
{
	int n = pow(2, x) - 1;
	return n;
}
int main()
{
	//生成ABC三根柱子
	char left = 'A', mid = 'B', right = 'C';
	//生成n个圆盘
	int n = 0;
	//输入圆盘的数量
	scanf("%d", &n);
	//通过汉诺塔函数实现移动
	HNT(left, mid, right, n);
	//移动完毕后打印圆盘移动次数
	printf("圆盘移动了%d次\n", num(n));
	return 0;
}

(4)涉及知识点

在汉诺塔问题通过函数递归的求解过程中我们涉及到了一下知识点:

  • 函数的组成
  • 函数的参数
  • 函数的传值调用
  • 函数的嵌套调用与链式访问
  • 函数的声明和定义
  • 函数递归

这个问题算是对函数知识点的综合考察,咱们只有将这些知识点真正做到融会贯通了才能从容的应对这类问题,所以咱们还要继续努力才行呀!!!

后面有机会我会再跟大家探讨一下通过函数迭代的方式来解决汉诺塔问题。

结语

到这里咱们本章的内容就全部结束了,希望这些内容能够帮助大家更好的理解并能独立编写汉诺塔问题。接下来随着学习的深入,我会继续给大家分享我在学习过程中的感受。如果各位喜欢博主的内容,还请给博主的文章点个赞支持一下,有需要的朋友也可以收藏起来反复观看哦!感谢各位的翻阅,咱们下一篇见。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值