PTA 汉诺塔的非递归实现

  • 7-11 汉诺塔的非递归实现 (25分)

借助堆栈以非递归(循环)方式求解汉诺塔的问题(n, a, b, c),即将N个盘子从起始柱(标记为“a”)通过借助柱(标记为“b”)移动到目标柱(标记为“c”),并保证每个移动符合汉诺塔问题的要求。

输入格式:
输入为一个正整数N,即起始柱上的盘数。

输出格式:
每个操作(移动)占一行,按柱1 -> 柱2的格式输出。

输入样例:
3
输出样例:
a -> c
a -> b
c -> b
a -> c
b -> a
b -> c
a -> c

如下面这段代码一样。递归代码往往都十分简洁,分析出递归出口和递归规则以后也比较容易实现,但是每一次的递归调用都需要压栈占用内存,效率不高.所以才需要递归转非递归来提高效率,减轻函数栈的负担。

#include<iostream>
#include<cstdio>using namespace std;void move(int n, char x, char y, char z);
//将n个盘子从x借助y移动到z
int main()
{int n;scanf("%d", &n);move(n, 'a', 'b', 'c');return 0;
}
void move(int n, char x, char y, char z)
{if(n == 1)printf("%c -> %c\n",x,z);else{move(n - 1, x, z, y);  //将n-1个盘子从x借助z移动到y上printf("%c -> %c\n",x,z);  //将最底下的第n个盘子从x移动到z上move(n - 1, y, x, z);  //将n-1个盘子从y借助x移动到z上}
}

递归求解汉诺塔问题,能解决问题,但不是我们真正想做的。
非递归求解汉诺塔问题给出以下三种方案。

1.方案一:
这段代码是实现了一位美国学者对于汉诺塔问题提出的解决方案(第一次写不太会写非递归,就去百度翻,翻到了这个自己觉得可以实现的算法),用3个stack栈来模拟三根柱子,用一个长度为3的char数组对应三根柱子的名字(‘a’,‘b’,‘c’),一方面便于输出由哪一根移动到哪一根,另一方面也是由于这种算法会因为盘子数的奇偶来简单调整后两根柱子的顺序。
算法介绍:一位美国学者发现一种出人意料的简单方法,只要轮流进行两步操作就可以了。
若n为偶数,柱子摆放为 A B C;若n为奇数,柱子摆放 A C B。
(1)把圆盘1从现在的柱子移动到下一根柱子,即当n为偶数时,若圆盘1在柱子A,则把它移动到B;若圆盘1在柱子B,则把它移动到C;若圆盘1在柱子C,则把它移动到A。
(2)接着,把另外两根柱子(圆盘1移动前所在的柱子以外的那两根柱子)上可以移动的圆盘移动到新的柱子上。即把非空柱子上的圆盘移动到空柱子上;当两根柱子都非空时,移动较小的圆盘。
(3)反复进行(1)(2)

操作3阶汉诺塔的移动:A→C,A→B,C→B,A→C,B→A,B→C,A→C

#include <iostream>
#include <stack>
#include <cstdio>using namespace std;char column_name[3] = {'a', 'b', 'c'};
stack<int> column[3];
int main()
{int n, move_count = 0, flag = 2, temp;scanf("%d", &n);for(int i = 0; i < n; i++){column[0].push(n - i);//将原盘从大到小放到第一根柱子上}if(n % 2 == 1)//如果n是奇数,交换后两根柱子的位置{char alpha;alpha = column_name[1];column_name[1] = column_name[2];column_name[2] = alpha;flag = 1;}while(true){if((int)column[flag].size() != n)//对应算法中的步骤(1),移动圆盘1{printf("%c -> %c\n", column_name[(move_count) % 3], column_name[(move_count + 1) % 3]);temp = column[(move_count) % 3].top();column[(move_count) % 3].pop();column[(move_count + 1) % 3].push(temp);}//对应算法中的步骤(2),把另外两根柱子上可以移动的圆盘移动到新的柱子上if((!column[(move_count) % 3].empty()) && (!column[(move_count + 2) % 3].empty())){//当两根柱子都非空时,移动较小的圆盘。if(column[(move_count) % 3].top() - column[(move_count + 2) % 3].top() > 0  && (int)column[flag].size() != n){printf("%c -> %c\n", column_name[(move_count + 2) % 3], column_name[(move_count) % 3]);temp = column[(move_count + 2) % 3].top();column[(move_count + 2) % 3].pop();column[(move_count ) % 3].push(temp);}else if(column[(move_count) % 3].top() - column[(move_count + 2) % 3].top() < 0  && (int)column[flag].size() != n){printf("%c -> %c\n", column_name[(move_count) % 3], column_name[(move_count + 2) % 3]);temp = column[(move_count ) % 3].top();column[(move_count ) % 3].pop();column[(move_count + 2) % 3].push(temp);}}else{if(column[(move_count) % 3].empty() && (int)column[flag].size() != n){printf("%c -> %c\n", column_name[(move_count + 2) % 3], column_name[(move_count) % 3]);temp = column[(move_count + 2) % 3].top();column[(move_count + 2) % 3].pop();column[(move_count ) % 3].push(temp);}else if(column[(move_count + 2) % 3].empty()  && (int)column[flag].size() != n){printf("%c -> %c\n", column_name[(move_count) % 3], column_name[(move_count + 2) % 3]);temp = column[(move_count ) % 3].top();column[(move_count ) % 3].pop();column[(move_count + 2) % 3].push(temp);}}move_count++;if((int)column[flag].size() == n)break;}return 0;
}

可能光是文字还是不太好理解,以三个圆盘为例具体的移动过程如下。


A->C

A→B

C→B


A→C


B→A

B→C

A→C
2.方案二
从我的一位同学那里学习到的,代码的作者是他,我简单做了些修改,添加了一些注释便于理解。比第一种的可迁移性要高很多,理解起来的难度可能和第一个差不多(因人而异把,我自己觉得第一个可能更好理解些嘻嘻),因为是去模拟函数栈的工作过程,所以代码也相当简洁。
与递归函数高度对应,具体怎么对应参见主函数注释,用一个自己写的stack来辅助模拟函数递归的过程,有种生拆的感觉O(∩_∩)O哈哈~。

#include <iostream>
#include <cstdio>using namespace std;const int defaultSize = 10000;class hanoi
{public:int n;char a1;char a2;char a3;hanoi(int _n = 5):n(_n), a1('a'), a2('b'), a3('c'){}hanoi(int _n, char _a1, char _a2, char _a3):n(_n),a1(_a1), a2(_a2), a3(_a3){}};class Stack
{hanoi* data;int top;int maxSize;
public:Stack(int s_size = defaultSize):data(NULL), top(-1), maxSize(defaultSize){data = new hanoi[s_size];}bool IsFull(){return top == (maxSize - 1);}bool IsEmpty(){return top == -1;}bool Pop(){if(IsEmpty())return false;top--;return true;}bool Push(hanoi x){if(IsFull())return false;data[++top] = x;return true;}hanoi getTop(){return data[top];}
};int main()
{int n;scanf("%d", &n);if(n < 1)return 0;Stack a;hanoi x(n);a.Push(x);//对应主函数中的传参,因为只有1个参数的构造函数会将三根柱子//依次赋值为'a','b','c',所以依然是对应的while(!a.IsEmpty()){if(a.getTop().n == 1){//cout << a.getTop().a1 << " -> " << a.getTop().a3 << endl;printf("%c -> %c\n", a.getTop().a1, a.getTop().a3);a.Pop();continue;}hanoi x1(a.getTop().n - 1, a.getTop().a1, a.getTop().a3, a.getTop().a2);//第一个参数为1是为了利用循环中a.getTop()为1时会移动并打印一次移动过程hanoi x2(1, a.getTop().a1, a.getTop().a2, a.getTop().a3);hanoi x3(a.getTop().n - 1, a.getTop().a2, a.getTop().a1, a.getTop().a3);a.Pop();//本着栈后进先出的工作原理,所以调整三者的入栈顺序a.Push(x3);a.Push(x2);a.Push(x1);}return 0;
}

同样的,以三个盘子的情况为例,具体的过程大概是这样的


3.方案三
这段代码的作者也不是我(T ^ T),是来自我的一位舍友,这种递归转非递归的可迁移性我觉得应该是三者之中最高的,但是可能也是最难理解的一种。此种方法模拟的是函数工作栈的过程,是去模拟类似codeblocks中call stack的进入和返回的过程。
对于递归代码,每当当前工作栈的n值为1时,需要进行打印。而因为原递归函数中,每当把总问题或者原子问题的n - 1个盘子从a借助c移动到b时,递归代码else中的这一步就逐步返回了,此时它第二次出现在栈顶。进行递归代码else中的第二行,也需要进行打印。这就是非递归代码中if部分的原理。
而为了标记它第几次出现在栈顶,我们加一个mark标记,而刚进入栈顶(mark由0变成1)时,我们应该做的是先处理原子问题,所以应该让n为n - 1的结点后入栈而出现在栈顶。这是非递归代码中else的if分支的原理。
若其又一次进入栈顶(mark由1变成2),意味着下一次一定会是第二次出现在栈顶,所以下一次是需要打印这个mark为2的结点的信息的(严格的说不能叫做结点,而是函数工作栈的一个元素块),所以要让当前栈顶结点也就是n为n(当前所在函数中给的形参对应的元素块)的结点入栈。这是非递归代码中else的else if分支的原理。

#include <iostream>
#include <stdio.h>
#include <stack>using namespace std;struct Node
{int n;char a;char b;char c;int mark = 0;Node(int N, char A, char B, char C){n = N;a = A;b = B;c = C;}
};
stack<Node> Hanno;
int main()
{int n;//scanf("%d", &n);cin >> n;Node hano(n, 'a', 'b', 'c');Hanno.push(hano);while (!Hanno.empty()){hano = Hanno.top();if (Hanno.top().n == 1 || Hanno.top().mark == 2){printf("%c -> %c\n", Hanno.top().a, Hanno.top().c);Hanno.pop();}else{if (Hanno.top().mark == 0){hano.mark++;Hanno.pop();Hanno.push(hano);Hanno.push(Node(hano.n - 1, hano.a, hano.c, hano.b));}else if (Hanno.top().mark == 1){hano.mark++;Hanno.pop();Hanno.push(Node(hano.n - 1, hano.b, hano.a, hano.c));Hanno.push(hano);}}}return 0;
}

这道题画出来的模拟图其实和第二种方案差不多,但会由于标记,比第二种过程少一些,具体的图就不画出来了,但是可以根据代码去自己画一下。可能是由于汉诺塔问题已经比较成熟了,出现了多种解题方案,第一种毕竟有些就题论题,还是应该多试着写一些后两种这样的通用的递归转非递归的代码。第一篇博客就到此结束啦Hi~ o( ̄▽ ̄)ブ。

PTA 汉诺塔的非递归实现相关推荐

  1. 7-5 汉诺塔的非递归实现 (25 分)

    7-5 汉诺塔的非递归实现 (25 分) 借助堆栈以非递归(循环)方式求解汉诺塔的问题(n, a, b, c),即将N个盘子从起始柱(标记为"a")通过借助柱(标记为"b ...

  2. 7-107 汉诺塔的非递归实现 (25 分)

    7-107 汉诺塔的非递归实现 (25 分) 借助堆栈以非递归(循环)方式求解汉诺塔的问题(n, a, b, c),即将N个盘子从起始柱(标记为"a")通过借助柱(标记为" ...

  3. 汉诺塔的非递归实现(借助堆栈模拟递归)

    汉诺塔的非递归实现 借助堆栈以非递归(循环)方式求解汉诺塔的问题(n,a,b,c).即将n个盘子从起始柱(标记为"a")通过借助柱(标记为"b")移动到目标杜( ...

  4. 7-17 汉诺塔的非递归实现 (25 分)(思路分析)

    一:题目 借助堆栈以非递归(循环)方式求解汉诺塔的问题(n, a, b, c),即将N个盘子从起始柱(标记为"a")通过借助柱(标记为"b")移动到目标柱(标记 ...

  5. 习题3.10 汉诺塔的非递归实现 (25分)

    借助堆栈以非递归(循环)方式求解汉诺塔的问题(n, a, b, c),即将N个盘子从起始柱(标记为"a")通过借助柱(标记为"b")移动到目标柱(标记为&quo ...

  6. 汉诺塔python非递归实现,[Python3 练习] 006 汉诺塔2 非递归解法

    题目:汉诺塔 II 接上一篇 [Python3 练习] 005 汉诺塔1 递归解法 这次不使用递归 不限定层数 (1) 解决方式 利用"二进制" (2) 具体说明 统一起见 我把左 ...

  7. 汉诺塔问题的递归和非递归算法

    汉诺塔问题是源于印度一个古老传说的益智玩具.大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘.大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上 ...

  8. python堆栈汉诺塔非递归_汉诺塔问题的递归解法和非递归解法(python语言实现)...

    汉诺塔问题的非递归解法(python语言类解法) #!/usr/bin/env python #coding:utf-8 import sys import time reload(sys) sys. ...

  9. 汉诺塔问题的递归求解

    汉诺塔问题的递归求解 汉诺塔 解题思路 具体实现 汉诺塔 汉诺塔 汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具.大梵天创造世界的时候做了三根金刚石柱子,在一根柱 ...

最新文章

  1. matlab方波假频现象分析,基于MATLAB 的信号时域采样及频率混叠现象分析
  2. 第二章 创建webGL设备和绘制缓冲区呈现 Context Creation and Drawing Buffer Presentation
  3. window.location操作url对象
  4. 运维工程师是桥的护栏_桥梁专家:钢结构桥比混凝土桥易涡振 以后出现涡振可能性会提高...
  5. 不同角色进入使用不同功能_如何在不同页面上使用不同的Joomla CSS
  6. oracle 一些基本概念
  7. ios 如何获得系统时间和日期
  8. H3C路由器映射端口到外网
  9. uva 11991 - Easy Problem from Rujia Liu?(STL)
  10. 2017年十大移动应用开发的测试工具
  11. 全网最详细SIFT算法原理实现
  12. 小米商城网页制作(附源码)
  13. Linux_Ubuntu快捷键截屏
  14. Learning Continuous Image Representation with Local Implicit Image Function解读
  15. tf.squared_difference函数
  16. 通过证书管理解决无法连接 Citrix XenApp SSL 61 您还未选择信任证书颁发者的问题
  17. gmt绘制中国省界,结合awk和RGB列表绘制中国地震台站分布
  18. c语言rand再哪个头文件里,c语言下rand函数在哪个头文件中
  19. #### Kafka Rebalance ####
  20. 如何免费低价获取一切资源?

热门文章

  1. 下载!微软出品的最新 Kubernetes 学习指南 3.0
  2. Python-字符串编码
  3. 软考 | 2019年下半年 软件设计师 下午试卷
  4. Citrix XenServer虚拟机安装详细过程图文教程
  5. 第17条:实现description方法
  6. 双向链表基本操作(C语言)
  7. 使用Redis做Mybatis的二级缓存
  8. kaggle简单使用教程(代码查找.下载、项目建立.运行、参加比赛)
  9. HPM6750系列--第五篇 使用Segger Embedded Studio for RISC-V开发环境
  10. 1加7 pro (oneplus7 pro) drivedroid测试