2021-05-28

九连环递归解法

最近忽然对递归迷上了,对九连环又比较感兴趣,听说九连环有递归解法,就研究了一下,搞得我上班都还在想。以前实现过九连环的递归,不过那一次是计算解连环需要多少步,今天具体分享一下解九连环的步骤输出的个人看法。

目录

  • 1.九连环递归思路

  • 2.解九连环总步骤数递归实现

  • 3.解连环具体步骤的输出

  • 4.写递归过程遇到的问题

  • 5.个人想法

1.九连环解法递归思路

简要对后面的习惯表达做一下说明,上第n环是指将第n个连环从架子下面挂到架子上,下第n环同理,上前n个环是指把第一到第n个没有遗漏地全部上到架子上,下前n个环类似。
九连环是一种古老的游戏,由于环环相扣,解开时需要有一些技巧,对于每个环,只有两种操作,上环和下环,想要解下或者上上第n个环必须要上上它前面那个环,即第n-1环,而且前面不能有环,架子上只有第n-1个和第n个环,这样才能上第n个环,上第n个环必须保证架子上只有第n环的前面一个环(第n-1环)。通过递归抽象,我们可以把下九连环的步骤归纳为如下:

  1. 下前n-2个环,对于九连环来说就是下掉前7个环,这时架子上只剩下第n-1和第n个环(第8环和第9环);
  2. 利用第n-1个环(第8环)下掉第n个环(第9环);
  3. 上上前n-2个环,这时问题就变成下前n-1个环(前8个)了,问题规模变小,而且是与解九连环同样的问题了,我们就可以用递归了;
  4. 下前n-1个环;

但是我们看到下九连环的步骤实际用到了上九连环的方法和步骤,而上九连环的步骤与下九连环不同。因此我们还需要构建上环的方法。仔细研究发现上环其实也是个递归过程,例如我们要上前n个环为例:

1.上前n-1个环;
2.下前n-2个环,架子上只剩下,第n-个环;
3.上第n个环;
4.上前n-2个环;

从上连环和下连环的步骤我们可以看出,下连环需要借助上连环的步骤,上连环也需要借助下连环的步骤,二者步骤很类似,但顺序和逻辑不同。实际上上连环和下连环所需要的次数是一样的,只是手法有差别。因此我们可以先讨论解九连环需要的步骤。

2.解九连环总步骤数递归实现

从第一章的解九连环思路我们可以得出解九连环所需总步数。设下n连环总步数为F1(n),上n连环总步骤数为F2(n),上或下第n个环只需1步。那么,我们就可以得出总步数的表达式:
F1(n) = F1(n-2) + 1+F2(n-2)+F1(n-1);
由于上连环和下连环步骤数一样因此F2(n) = F1(n)=F(n),用F(n)简便表示,即:
F(n) = F(n-2) + 1+F(n-2) + F(n-1)= 2*F(n-2)+F(n-1) + 1;
化简到这个形式对于递归已经够了。
在上代码之前做一下说明,这里的n必须要大于2,对于小于2等于·1的情况做一下说明,架子上只有两个前两个环的时候,两个环是可以一起上上或者取下的,如果你玩过九连环的话就知道。第一个环可以自由取下或上上;
因此总步数的代码入下


#include <stdio.h>

int JiuLianCount(int n)
{
	if(n ==1 || n== 2)
	{
		return 1;
	}
	else
	{
		return (JiuLianCount(n-2)*2 + JiuLianCount(n-1) +1);
	}
	
 } 
 
 int main()
 {
 	int sum;
 	sum = JiuLianCount(9);
 	printf("%d\n", sum);
 	return 0;
 }

有的方法采用解前两个环使用两步的也可,步骤会更多一些。

3.解连环具体步骤输出

这个就比纯粹计算次数难度要大一些。但是也有方法可循,例如对于环来说有两种操作,上环和下环,因此我们需要构造这两种过程,依据是我们第一章解九连环的思路。为此我们构造两个过程

上前n个环

  1. 下前n-2个环;
  2. 下第n个环;
  3. 上前n-2个环;;
  4. 下前n-1个环;

下前n个环
1.上前n-1个环;
2.下前n-2个环;
3.上第n个环;
4.上前n-2个环;
两个过程相互调用,可以考虑写在一个递归过程里面,有标志位判断上环还是下环,上环则调用上环的部分,下环则调用下环的部分。比较不好理解的就是上环和下环的过程都既调用自身,也相互调用,执行过程有点费解,不过多想想就能想通。
下面上代码


//stdbool.h是使用bool类型的库名,需要包含在程序里
//“1v”表示下第1个环,“1^”表示上第1个环,“n^”和“nv”同理

#include <stdio.h>
#include <stdbool.h>

//UpDown = true,下连环,UpDown = false,上连环 
void JiuLianHuan(int n , bool UpDown)
{
	if((n == 1) && (UpDown == true))	//递归出口1,下第1个环 
	{
		printf("1v\t");	
	}
	else if((n ==2) && (UpDown == true))	//递归出口2,下第1和2个环 
	{
		printf("1v 2v\t");
	}
	else if((n == 1) && (UpDown == false))	//递归出口3,上第1个环 
	{
		printf("1^\t");
	}
	else if((n == 2) && (UpDown == false))	//递归出口4,上第1和2个环
	{
		printf("1^ 2^\t");
	} 
	else if(UpDown == true)				//下前n个环递归体 
	{
		JiuLianHuan(n-2, true);
		printf("%dv\t",n);
		JiuLianHuan(n-2, false);
		JiuLianHuan(n-1, true);
		printf("\n");				//换行调整格式 
	}
	else if(UpDown == false)
	{
		JiuLianHuan(n-1, false);
		JiuLianHuan(n-2, true);
		printf("%d^\t", n);
		JiuLianHuan(n-2, false);
		printf("\n");			//换行调整格式
	}
}

int main()
{
	JiuLianHuan(9, true);
	return 0;
 } 

也可以采用上环下环分开写的形式

#include <stdio.h>
#include <stdbool.h>

void Xia(int n);
void Shang(int n);

void Xia(int n)
{
	if(n == 1)	//递归出口1,下第1个环 
	{
		printf("1v\t");	
	}
	else if(n ==2)	//递归出口2,下第1和2个环 
	{
		printf("1v 2v\t");
	}
	else 				//下前n个环递归体 
	{
		Xia(n-2);		//递归第1步
		printf("%dv\t",n);		//递归第二步
		Shang(n-2);	//递归第三步
		Xia(n-1);	//递归第四步
		printf("\n");				//换行调整格式 
	}
}

void Shang(int n)
{
	if(n == 1)	//递归出口1,上第1个环 
	{
		printf("1^\t");
	}
	else if(n == 2)	//递归出口2,上第1和2个环
	{
		printf("1^ 2^\t");
	}
	else
	{
		Shang(n-1);
		Xia(n-2);
		printf("%d^\t", n);
		Shang(n-2);
		printf("\n");			//换行调整格式
	}
}



int main()
{
	Xia(9);
	return 0;
 } 

这个递归执行规程太长就不演示结果了。

两种方法其实过程十分类似,只是第二种相互拆开,可能第二种更符合人的思维。

简要说明一下递归的规程,就以第二种递归方式为例吧,上环和下环过程分开好理解一些。首先将上环和下环看成两个独立的过程,下环过程第一步是下前n-2(前7个环)个环,因此递归调用自身,层数是n-2,因为n-2(现在是7)在递归函数的函数体里没有定义,栈里保存,所以继续往底层递归到第5层,仍然没有定义,到第3层,没有定义,到第一层,有定义,打印输出一个“1v",然后第一层执行完毕,可以返回,函数到这里有了出口,但是递归还没结束,栈里面的第3,5,7,9层都还没执行完毕,这时返回第三层的下环调用,但是第三层调用到此也还没结束,因为第三层调用的函数没有完全执行完毕,因为第三层下连环有四步递归,第一层跳出去是因为我们做了规定,如果是第一层,打印,然后函数执行完毕。所以第三层继续执行递归第二步,打印,这一步是非递归,打印出来就完成,然后执行第三层的第三步,这一步是调用上前三个环的函数,会跳到Shang()这个函数里面。

我们发现,这个上环的函数也是个递归,而且不仅调用自身,还调用Xia()这个函数,这里有些不好理解。Xia这个函数不仅在自己的函数体内调用自己,还在调用别的函数,这个别的函数里面也调用自己,像这种,通过调用别的函数而间接调用自身的过程,我们称为间接递归,在自己的函数体内调用自己的称为直接递归。显然,在Xia()这个函数里面既有直接递归,又有间接递归。第三步调用Shang()函数,过程与Xia()类似,也会执行完毕返回,然后执行第四步,这一步是直接递归,不再继续分析。执行完这四个过程之后,第三层递归完毕,然后函数会返回到第五层递归体的第一步,因为,第五层就是在这里调用第三层的。然后等第五层的四步执行完毕后,返回第七层的递归体的第一步,又等执行完毕,函数返回第九层递归体的第一步,然后依次把第九层的递归体执行完毕,递归过程结束,但实际上第九层的后面几步执行完毕需要的步骤几乎和返回第九层一样长。

这就是连环的智慧,不仅要求相邻两个环互相配合使用上下环,而且下环过程使用上环过程,上环过程也使用下环过程。

4.写递归遇到的问题

写这个递归时,我一开始没有把下环和上环的逻辑分开,只写了上环的逻辑,运行的时候发现逻辑不对,后来才想清楚,虽然上环过程也有下环调用,但是上环过程里的下环调用只是服务于下环过程,并不为上环过程服务,从函数执行的角度来讲,下环要是一个单独的函数或者过程,否则不能被执行,比如方法一上环标志位是fase,如果不给标志位为false的情况写执行过程,由于else语句的特性,找不到这种情况,因此跳过它。所以上环的过程都要实现,因为上环要调用下环的过程,下环也要调用上环的过程。

5.个人想法

我个人认为递归函数实际也是函数,只不过是自生调用自身,调用到底层(递归出口)的过程有点向一个性子急的人,一定要刨根问底,执行本层执行不下去了,立马调用下一层,后面没执行完成的步也不管了,就是要找到那个递归出口,找到之后返回最高层的过程又不是很急了,等底层全部执行完毕才能返回上层,打个不恰当的比方,由于工作需要你需要找公司的董事长开个证明,这里相当于最高层调用,董事长不了解你的情况,但是又不好直接调用基层,于是就说让你去找总经理去核实签字,再来,然后你跑到总经理那里,让他给你签字,总经理也不了解情况,就说,你去你们部门经理那里去核实签字,然后你去了部门经理那里,部门经理手下也有好多案子要处理,告诉你找你的主管,然后你就去找到了主管,主管一看,嗯,情况属实,签字(底层递归出口),然后你又跑到部门经理那里,属实,签字,依次这样才到了董事长那里,你终于搞到了证明。当然现在的公司一般不会采用这种方法了,就是需要一层层签核,也是电子档,不需要人跑来跑去的了。

写递归一定要注意三点,第一,递归关系,第二递归出口,第三,递归逼近。递归关系多以差分方程的形式表达,递归出口一定要写全,不要遗漏,否则就会形成死递归,永远也跳不出来,递归逼近,一定要保证每次递归,都越来越接近递归出口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值