汉诺塔(内部+伪图形)

1.1问题描述:

假设有三个分别命名为A,B,C的塔座,在塔座A上插有n个直径大小各不相同的圆盘,大的在下,小的在上,且从小到大编号为1,2,3…。现要求将塔座A上的n个圆盘移到塔座C上并仍按同样的顺序叠排,圆盘移动时必须遵守以下规则:
(1)每次只能移动一个圆盘
(2)圆盘可以插在A,B,C中任一塔上
(3)任何时刻都不能将一个较大的圆盘压在较小的圆盘之上

1.2题目要求:

(1)基本解:要求汉诺塔层数n,起始柱,结束柱由键盘输入;打印出移动的盘子编号以及从哪根柱子移动到哪根柱子的具体步骤。
(2)基本解(步数记录):在(1)的基础上要求记录移动步数。
(3)内部数组显示(横向):在(2)基础上加入延时效果,要求动态显示移动过程中每根柱子现有的盘子以及盘子编号,同时要求显示速度可由键盘输入进行选择;不允许使用gotoxy();
(4)内部数组显示(纵向+横向):在(3)的基础上,要求显示竖行。允许使用gotoxy函数,不建议使用system(“cls”);来实现清屏;内部数组是否显示可由键盘输入进行选择
(5)图形解-预备-画三个圆柱:要求加入延时效果
(6)图形解-预备-在起始柱上画n个圆盘:大的在下,小的在上;不同盘子不同颜色;要求加入延时效果。
(7)图形解-预备-第一次移动:要求移动时先上移,平移,然后下移;延时。
(8)图形解-自动移动版本:移动要求同(7);同时显示横向和纵向;显示速度可由键盘输入选择,0为手动;延时。
(9)图形解-游戏版:要求手动输入柱子编号,分别表示此次移动的起始柱和目标柱;对输入的合理性进行判断,如大盘压小盘,需要提示错误;要求记录步数;待所有盘子移动到结束柱是显示“游戏结束”。
(10)以上的要求都是对上兼容,即前面提到的要求若非特殊申明都是需要遵守的。
(11)要求利用递归方式完成,且递归函数不准超过15行。其余函数代码量尽量不超过50行。
(12)所有小题都要求检查输入数据的合理性(包括要求输入数字而输入字母的非法情况)。
(13)最后将汉诺塔所有的实现方式集中在一个程序中,用菜单方式由键盘输入进行选择。如图所示:
(14)代码要求:格式对齐,不允许使用goto语句,在VS2015环境下,0warning,0error;
不允许使用所学范围以外的内容(除了已经补充过的);允许用全局变量,全局数组的方式分别记录三根圆柱中的圆盘数,圆盘编号,总移动步数,其余不准用全局变量。

2.整体设计思路

面对相对复杂的程序,我选择将其拆分成若干个小问题去解决,最终整合成最终的程序。同时,在这些小问题中挑选出比较核心的问题,其他小问题都是围绕着它们进行编程,实现。

整个程序最核心的部分是如何利用递归来解决汉诺塔问题。其次是显示内部数组部分,思路是利用“栈”的思想,对栈顶的元素进行判断,移动(通过值的变化)。最后一部分,是伪图形界面下图形的移动和生成。思路是通过对光标位置的设定,用空格再“走”一遍图形“走”过的路。

3.主要功能的实现

3.1 利用最优的搬运方式解决汉诺塔问题—递归算法

分析Hanoi问题,本质上是不停的在做同一个操作,即从起始柱,借助中间柱将盘子转移到结束柱,因此可以设计成函数,不停的调用该函数,设置结束条件,即n=1时,回溯,即输出每一步的搬运过程,即源程序中的Hanoi函数。

3.2每根柱子盘子情况显示—“栈”

将每根柱子看成是一个数组,本质就是打印数组内部的值。盘子的移进移出都是对当下柱子最顶部的盘子有所操作,对数组而言,需要打印的元素个数就是当时柱子上圆盘的个数,操作是对于栈顶元素。

实现方法:
  • 定义二维数组pan[3][10],[3]代表三根柱子,[10]代表数组大小。
  • 初始化时,起始柱最底部的柱子初始化为n,即pan[begin][i]=n-i (i=0,1,2…n-1),这样同时完成了盘子的编号。其余两根柱子(数组)所有元素初始化为-1.通过元素编号来判断有没有盘子。
  • 操作过程中,先进先出,通过对栈顶元素值的变化模拟柱子盘子的搬动情况。用top_a,top_b分别表示“出发柱”和“到达柱”的栈顶盘子的位置,即数组中值大于0的元素个数-1.
  • “到达柱”接收一个盘子后,新的栈顶元素位置为top_b=top_b+1,pan[final][top_b]=pan[beg][top_a];
  • “出发柱”搬出一个盘子后,将原来的栈顶元素的值赋给“到达柱”新的栈顶元素,此时原来的栈顶元素所在的位置没有盘子,即pan[begin][top_a]=-1,同时,新的栈顶元素位置top_a=top_a-1。
  • 最后输出只需要判断元素的值是否大于0,是,则输出,输出即盘子的编号。
3.3图形方式显示时,盘子的移动

for循环,改变图形(对空字符设置颜色,长度)的位置,加入Sleep()函数实现延时效果,使整个过程更加直观,容易观察。
判断坐标变化时,在图形,也就是盘子左右移动时,每次都需要判断“出发柱”和“到达柱”的相对位置,来确定横坐标i++还是i- -以及空格字符打印的位置(i是打印时光标的位置,在图形的最左端)。为此,我添加了一个整型变量q=(f(x)>f(y)?-1:1),利用i+=q就可以达到i++/- -要求。(其中f()是自定义的函数将键盘输入的起始柱,目标柱的字符对应到数字,以方便于对应数组的对应)。
而空字符打印位置的确定,q=1时,图形右移,空字符打印在i的位置;q=-1时,图形左移,空字符打印在i+p的位置(p=图形长度-1)。

4.调试过程中遇到的问题

4.1 if(q) 和 if(q>0)

在图形左右移动过程中对空格添加的位置判断时,需要判断左移还是右移,左移q=-1,右移是q=1,分别对应不同的空格输出的位置。而我误以为if(q)的意思是q大于零时,而实际上,if(q)是当q不等于0时!这是一个认识性错误,也是最难找到的错误!耗时近三小时!

4.2 柱子底座有缺口

第八小题要求同时显示图形,竖式,横式。调试过程中发现搬运一开始,第一第二个底座就会出现一个缺口,大约一个字符宽度,而且正好就在竖式上方!对竖式的位置进行下移以后,显示正确!事实上,在竖式输出时程序的思路是,数组元素大于0输出元素的值,否则,输出空格!所以,虽然显示时没有,但其实还是存在并占据一定空间的!

4.3 圆盘上移“吃”下盘,下移“吐”柱子

顾名思义,就是圆盘上移时,下方的一个盘子会少一块,移到“到达柱”柱顶时,再下移时的同时,柱子会长一个字符的长度。
"吃"下盘,究其原因,其实是被“扫地”的黑前景黑后景空格字符“扫”干净了!圆盘上移的实现方法是:先在正下方打印黑色空字符串把圆盘原来的图形“扫干净”,再在圆盘正上方一个单位用showch函数打非黑色的字符串。而我没有考虑到圆盘刚刚“出发”时,正下方是另一个圆盘,或者是底座!
解决方法:
在上移循环体内加一句判断语句
if 纵坐标 < 圆盘初始坐标 then 做“扫地”操作

“吐”柱子,其实是多填补了一小段柱子!圆盘下移的实现方式是:先在正下方打印非黑色字符串的图形,再把原来字符串所在的位置用黑色字符串“扫干净”,此时,那一段范围内的柱子也被扫掉了,于是,再补一小段柱子!而当圆盘正好在柱子顶端时,下移后原来坐在的位置本就没有柱子,我却补了一段!
解决方法:
在下移循环体内也加一句判断
if 纵坐标 > 柱子顶部所在纵坐标 - 做“扫地”操作,同时填补柱子

5.心得体会

5.1经验&体会
5.1.1 经验教训

(1)反复出现的常量,宏定义在源程序开头,提高程序可维护性!
(2)定义函数或者变量时,名字尽量取得有意义!防止函数,变量较多时,不停的翻源程序看对应的功能。
(3)当程序规模比较大的时候,上下滚动查看函数很不方便!可以使用编程界面左侧 的“减号”把一些程序“叠”起来,如 ;
这样可以加快查找函数的速度,同时不至于满屏幕的代码看的眼花缭乱。
(4)在程序测试阶段,程序规模较大时,测试时修改的地方较多!为了防止把程序改“坏”之后回不到原来“好”的时候,可以在每次成功一小步后,把源代码复制到另外一个工程文件中,保存起来!
(5)遇到想不通的思路,流程时,充分利用纸笔来助我理通思路。

最后,利用函数编写代码某种程度上,是对我解决复杂问题时总体思路的要求!面对复杂的问题,最好的策略应该是将其拆分成若干个小问题!每个问题的解决都对应一个或若干个函数功能的实现!因此,我需要培养自己拆分出小问题的能力和习惯!

附上github链接供参考
Hanoi源码

  • 12
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值