【C语言】汉诺塔

标题C语言:汉诺塔

水墨不写bug 

 (图片来源于网络)


 正文开始:

        相信你在写函数递归的时候,一定会尝试一种最简单的递归,但实际上它并 不是 递归的 正确写法:

        e.g.1


#include<stdio.h>
int main()
{
	printf("hehe\n");

	main();

	return 0;
}

        你会发现:

        程序从main()函数进入后会再次调用main()函数,似乎永远不会停下来;

        结果:

        

         程序最终崩溃了,但是什么造成了程序崩溃?

        如果按F10调试,你会找到一条详细的报错信息:

        

        报错信息就是:stack overflow!

(一)由递归导致栈溢出

(1)汉诺塔

         相信你一定知道汉诺塔的问题:

         大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。


i,特殊的视角

        我们先从简单的情况开始分析:

        

        当移动4片时,在移动的过程中,一定会存在一个状态,在这个状态下

        1.最下层的4号盘子放在目标柱子上;(其他盘子的状态就显得不那么重要了)

        2.其他的盘子从上到下,从小到大,摆在第一个柱子上;(为了使游戏继续进行,不得不这样做)

        这时,我们完成了移动最底层的盘子。

        于是,此时盘子的状态:

        

        这时:

        你会发现,接下来就是 移动 三层盘子的问题了,相信对你来说这不是个难题。

ii,问题回归

        当我们尝试将n个盘子从  “ start ”  移动到  “ dest ”  时:

        1.可以将上面的(n-1)个盘子看作是一个整体,

        2.最下面一个盘子:第n个盘子看作与整体等地位的个体;


        于是,我们将n个盘子从  “ start ”  移动到  “ dest ”  的目标就可以分解成:

        先将上面(n-1)个盘子移动到  “ dest ”  ,再将第 n 个盘子移动到  “ dest ”  。

         但是,我们的力量是有限的,一次只能移动一个盘子。于是,我们可以再次动用上述方法;

        先将上面(n-2)个盘子移动到  “ dest ”  ,再将第 (n-1)个盘子移动到  “ dest ”  。

        ......

iii,问题解决

        在这里,为了表述方便,我引入  “  降n程序  ”  的概念:

        每执行一次降n程序,都会使 剩下的 需要移动的 盘子数 减 1 ;

        而每执行一次   “ 降 n 程序 ” ,都要移动上方的  (n-1)个盘子两次;

         在一次只能移动一个盘子的前提下,我们把移动 n 个盘子的操作称为  “ 移n操作 ”;

        试想:

        在上方(n-1)个盘子不等于 1 的情况下,由于我们每次只能移动一个盘子,所以——每次移动上方的 (n-1)个盘子,也就必须先移动上方的(n-2)个盘子,和第(n-1)个盘子。

        也就是说:

        1.一次  ”  将 n 程序 “ 包含两次  “移n操作” ;

        2.“移n操作想要完成,当且仅当n = 1 的时候才可以实现;

        于是,移动n个盘子需要的次数是:2^n - 1 次!(为什么要  ” -1 “呢?——因为当n = 1的时候,一次就可以完成移动了,不用再分)

(2)子过程

        当你回顾汉诺塔问题时,会发现:

        我们要移动n个盘子到目的地,就必须先移动上方(n-1)个盘子,而移动上方(n-1)个盘子,又要先移动上方(n-2)个盘子......

        也就是说,如果第(n-2)个盘子未完成移动,那么第2次的 ” 降n程序 “ 就没有完成!如果第(n-3)个盘子未完成移动,那么第3次的 ” 降n程序 “ 就没有完成!

......

        也就是说,深一层的递归是本层递归的一个子过程!

        如果这一过程发生在计算机的内存中呢?

        每一次递归都要在内存的栈区开辟一块新的空间。 

        这就很容易找到原因了。

(二)计算机的内存(为什么会栈溢出)

        其实,计算机的内存可以分为三个区域:栈区,堆区,静态区。

        它们在存储使用时遵循不同的规则,并且存储的内容也不同。

        如图:

         

         而我们每一次递归时,上一次的递归程序仍然没有结束,也就是上一次递归的函数仍然占据着内存栈区的空间;

        当我们递归调用函数没有跳出条件时(也就是e.g.1的情形),栈就会溢出。

        于是,当我们理解了栈溢出的原理,就会发现:

        递归程度太深也会造成栈溢出,

        e.g.2


#include<stdio.h>
void conduct(int n)
{
	if (n < 10000)
	{
		conduct(n + 1);
	}
}

int main()
{

	conduct(1);
	return 0;
}

        由于每一次递归时上一次调用的conduct()函数并没有结束,所以上一次调用的 conduct()函数在内存的栈区仍然占有空间。 

        程序的执行结果:

        

        仍然造成了 stack overflow,由此可见:当递归程度太深也会造成栈溢出。

(三)什么是栈溢出

        栈溢出的原因主要是程序访问了不合法的内存地址,导致写入了超过栈空间大小的数据,从而覆盖了栈中其他变量或函数的返回地址、参数和局部变量等数据。

(四)小结

        1.栈溢出是内存栈区被不合理使用造成的;

        2.写函数递归的时候要避免死递归;

        3.递归成都太深也会造成栈溢出;


回顾:

目录

(一)由递归导致栈溢出

(1)汉诺塔

i,特殊的视角

ii,问题回归

iii,问题解决

(2)子过程

(二)计算机的内存(为什么会栈溢出)

(三)什么是栈溢出

(四)小结


完~

未经作者同意禁止转载

  • 10
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
电子图书资源服务系统是一款基于 Java Swing 的 C-S 应用,旨在提供电子图书资源一站式服务,可从系统提供的图书资源中直接检索资源并进行下载。.zip优质项目,资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松copy复刻,拿到资料包后可轻松复现出一样的项目。 本人系统开发经验充足,有任何使用问题欢迎随时与我联系,我会及时为你解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(若有),项目具体内容可查看下方的资源详情。 【附带帮助】: 若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步。 【本人专注计算机领域】: 有任何使用问题欢迎随时与我联系,我会及时解答,第一时间为你提供帮助,CSDN博客端可私信,为你解惑,欢迎交流。 【适合场景】: 相关项目设计中,皆可应用在项目开发、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面中 可借鉴此优质项目实现复刻,也可以基于此项目进行扩展来开发出更多功能 【无积分此资源可联系获取】 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。积分/付费仅作为资源整理辛苦费用。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

水墨不写bug

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

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

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

打赏作者

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

抵扣说明:

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

余额充值