汉诺塔问题——递归算法与非递归算法

一、问题描述
       

         汉诺塔问题是一个经典的问题。汉诺塔(Hanoi Tower),又称河内塔,源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。问应该如何操作?

二、具体问题:

        给出一个二维数组V和change方法,要求给出递归过程代码。

change方法

void change(vector<vector<int>>& V, int a, int b)
{cnt++;int tmp = V[a].back();V[a].pop_back();V[b].push_back(tmp);cout << "(" << a+1 << "->" << b+1 << ")" << endl;
}

数组构建

int main()
{int n = 0;cin >> n;vector<vector<int>> V;V.resize(3);V[0].resize(n);for (int i = n; i > 0; --i){V[0][i - 1] = n - i + 1;}solve(V,...);
}

我们要写出solve方法的实现

三:递归思路分析

        递归求解,要找子问题,从结果出发。要将假设5个盘子放到第三个柱子上。这里我们要想子问题。

首先:

1.把问题转化为先把四个盘子放到第二个柱子上,把最大的盘子5号放到第三个柱子上,再把这四个盘子放到第三个柱子上。

接下来我们要考虑怎么把四个盘子放到第二个柱子上。

2.把这个问题转化为先把三个盘子放到第三个柱子上,把4号盘子放到第二个柱子上,再把这三个盘子放到第二个柱子上。

已经发现了规律:

3.把这个问题转化为先把二个盘子放到第二个柱子上,把3号盘子放到第三个柱子上,再把这二个盘子放到第三个柱子上。

4.把这个问题转化为先把一个盘子放到第三个柱子上,把2号盘子放到第二个柱子上,再把这一个盘子放到第二个柱子上。

找到了子问题:当只需要整体移动一个盘子时候,就是递归过程的叶子结点。

根据上边的分析,我们的递归函数,需要传参:  数组V , 整体移动几个盘子n,移动其实柱子from,移动终点柱子dest 。

注意,我们每次整体移动时候,都会转化为把除了

最大的盘子其他的放到第三者柱子上,这个第三者是除了from与dest的第三者。所以我们考虑用一个异或的方式来处理

以下是代码过程:

int p = 0^1^2;void solve(vector<vector<int>>& V, int n,int from, int dest)
{if (n == 1){change(V, from, dest);return;}solve(V, n - 1, from, p ^ from ^ dest);change(V, from, dest);solve(V, n - 1, p ^ from ^ dest,dest);
}	

整体代码:

#include<iostream>using namespace std;
#include<vector>
#include<string>
int cnt = 0;void change(vector<vector<int>>& V, int a, int b)
{cnt++;int tmp = V[a].back();V[a].pop_back();V[b].push_back(tmp);cout << "(" << a+1 << "->" << b+1 << ")" << endl;
}int p = 0^1^2;void solve(vector<vector<int>>& V, int n,int from, int dest)
{if (n == 1){change(V, from, dest);return;}solve(V, n - 1, from, p ^ from ^ dest);change(V, from, dest);solve(V, n - 1, p ^ from ^ dest,dest);
}	int main()
{int n = 0;cin >> n;vector<vector<int>> V;V.resize(3);V[0].resize(n);for (int i = n; i > 0; --i){V[0][i - 1] = n - i + 1;}solve(V, n, 0, 2);for (int i = 0; i < n; ++i){cout << V[2][i] << "->";}cout<<endl << cnt << endl;return 0;
}

运行结果,以5为例:

四、非递归思路分析:

进行非递归分析就得找循环。从1开始逐步分析其中的循环结构。

比较容易知道,1和2是需要特殊处理的。接下来对于三个盘子,我们的步骤是:

n == 1 :     change(V, 0, 2);

n == 2 :

     change(V, 0, 1);
     change(V, 0, 2);
     change(V, 1, 2);

n==3 : 

     change(V, 0, 2);
     change(V, 0, 1);
     change(V, 2, 1);

     change(V, 0, 2);
     change(V, 1, 0);
     change(V, 1, 2);

     change(V, 0, 2);
 

        

对于四个盘子,我们要先把三个盘子放到第二个柱子上,移动最大盘子,再把这三个盘子放到第三个柱子上。

对于五个盘子,我们要先把四个盘子放到第二个柱子上,就得先把三个盘子放到第三个柱子上,然后移动第四个盘子到第二个柱子上,再把三个盘子放到第二个柱子上。

然后把第五个盘子放到第三个柱子上,接着就是第二个柱子上的四个盘子放到第三个柱子上,就得先把三个盘子放到第一个柱子上,

把第四个柱子放到第三个柱子上后,就是最后一步把一号柱子上的三个盘子放到第三个柱子上。

我们初步发现了规律:奇数盘子和偶数盘子是不一样的(三个盘子对于奇数应该放到原本柱子的后两个柱子上,偶数则是接下来的一个),我们要分类讨论这两种情况。每次把三个盘子整体移动过后,我们就得把剩下两个柱子上的小盘子放到大盘子上,(如果有柱子为空另说)

所以得出了代码逻辑:

void solve(vector<vector<int>>& V,int n,int now)
{while (1) {if (V[2].size() == n)return;if (n == 1) { change(V, 0, 2); cout << endl; return solve(V, n, 2); }else if (n == 2) {change(V, 0, 1);change(V, 0, 2);change(V, 1, 2);cout << endl;return solve(V, n, 2);}else if (n % 2 == 1) {change(V, now, (now + 2) % 3);change(V, now, (now + 1) % 3);change(V, (now + 2) % 3, (now + 1) % 3);change(V, now, (now + 2) % 3);change(V, (now + 1) % 3, now);change(V, (now + 1) % 3, (now + 2) % 3);change(V, now, (now + 2) % 3);if (!V[now].empty() && !V[(now + 1) % 3].empty()) {int big = now;int sma = (now + 1) % 3;if (V[big].back() < V[(now + 1) % 3].back()) big = (now + 1) % 3, sma = now;change(V, sma, big);}else if (V[now].empty() && V[(now + 1) % 3].empty()) {}else if (!V[now].empty()){change(V, now, (now + 1) % 3);}else {change(V, (now + 1) % 3, now);}now = (now + 2) % 3;}else {change(V, now, (now + 1) % 3);change(V, now, (now + 2) % 3);change(V, (now + 1) % 3, (now + 2) % 3);change(V, now, (now + 1) % 3);change(V, (now + 2) % 3, now);change(V, (now + 2) % 3, (now + 1) % 3);change(V, now, (now + 1) % 3);if (!V[now].empty() && !V[(now + 2) % 3].empty()) {int big = now;int sma = (now + 2) % 3;if (V[big].back() < V[(now + 2) % 3].back()) big = (now + 2) % 3, sma = now;change(V, sma, big);}else if (V[now].empty() && V[(now + 2) % 3].empty()) {}else if (!V[now].empty()){change(V, now, (now + 2) % 3);}else {change(V, (now + 2) % 3, now);}now = (now + 1) % 3;}}
}

整体代码

#include<iostream>using namespace std;
#include<vector>
#include<string>#define NUM 3
int cnt = 0;
void change(vector<vector<int>>& V, int a, int b)
{cnt++;int tmp = V[a].back();V[a].pop_back();V[b].push_back(tmp);string res;cout << "(" << a << "->" << b << ")" << endl;
}void solve(vector<vector<int>>& V,int n,int now)
{while (1) {if (V[2].size() == n)return;if (n == 1) { change(V, 0, 2); cout << endl; return solve(V, n, 2); }else if (n == 2) {change(V, 0, 1);change(V, 0, 2);change(V, 1, 2);cout << endl;return solve(V, n, 2);}else if (n % 2 == 1) {change(V, now, (now + 2) % 3);change(V, now, (now + 1) % 3);change(V, (now + 2) % 3, (now + 1) % 3);change(V, now, (now + 2) % 3);change(V, (now + 1) % 3, now);change(V, (now + 1) % 3, (now + 2) % 3);change(V, now, (now + 2) % 3);if (!V[now].empty() && !V[(now + 1) % 3].empty()) {int big = now;int sma = (now + 1) % 3;if (V[big].back() < V[(now + 1) % 3].back()) big = (now + 1) % 3, sma = now;change(V, sma, big);}else if (V[now].empty() && V[(now + 1) % 3].empty()) {}else if (!V[now].empty()){change(V, now, (now + 1) % 3);}else {change(V, (now + 1) % 3, now);}now = (now + 2) % 3;}else {change(V, now, (now + 1) % 3);change(V, now, (now + 2) % 3);change(V, (now + 1) % 3, (now + 2) % 3);change(V, now, (now + 1) % 3);change(V, (now + 2) % 3, now);change(V, (now + 2) % 3, (now + 1) % 3);change(V, now, (now + 1) % 3);if (!V[now].empty() && !V[(now + 2) % 3].empty()) {int big = now;int sma = (now + 2) % 3;if (V[big].back() < V[(now + 2) % 3].back()) big = (now + 2) % 3, sma = now;change(V, sma, big);}else if (V[now].empty() && V[(now + 2) % 3].empty()) {}else if (!V[now].empty()){change(V, now, (now + 2) % 3);}else {change(V, (now + 2) % 3, now);}now = (now + 1) % 3;}}
}int main()
{int n = 0;cin >> n;vector<vector<int>> V;V.resize(3);V[0].resize(n);for (int i = n; i > 0; --i){V[0][i-1] = n-i+1;}solve(V,n, 0);for (int i = 0; i < n; ++i){cout << V[2][i]<<"->";}cout << endl << cnt << endl;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/469908.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件架构设计--操作系统

常见I/O类型:缓冲I/O和直接I/O 缓冲I/O是C语言提供的库函数 直接I/O是Linux系统API 应用程序内存:代码中用malloc/free,new/delete 等分配出来的内存 用户缓冲区:C语言的FILE结构体里面的bufer. 内核缓冲区:Linux操作系统的Page Cache. 1Page 一般为4K 缓冲I/O的读操作有3次数…

Rust 数据结构与算法:2线性数据结构 之 栈

二、基础数据结构 1、线性数据结构 数组、栈、队列、双端队列、链表这类数据结构都是保存数据的容器,数据项之间的顺序由添加或删除时的顺序决定,数据项一旦被添加,其相对于前后元素就会一直保持位置不变,诸如此类的数据结构被称为线性数据结构。线性数据结构有两端,称为…

【王道数据结构】【chapter5树与二叉树】【P159t14】

设有一棵满二叉树&#xff08;所有结点值均不同&#xff09;&#xff0c;已知其先序序列为pre&#xff0c;设计一个算法求其后序序列post #include <iostream> #include <stack> #include <queue> #include<string.h> typedef struct treenode{char da…

UI文件原理

使用UI文件创建界面很轻松很便捷&#xff0c;他的原理就是每次我们保存UI文件的时候&#xff0c;QtCreator就自动帮我们将UI文件翻译成C的图形界面创建代码。可以通过以下步骤查看代码 到工程编译目录&#xff0c;一般就是工程同级目录下会生成另一个编译目录&#xff0c;会找到…

代码随想录 Leetcode134. 加油站

题目&#xff1a; 代码(首刷看解析 2024年2月15日&#xff09;&#xff1a; class Solution { public:int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {int curSum 0;int sum 0;int startIndex 0;for (int i 0; i < gas.size(); i)…

【JavaScript 教程】

JavaScript 教程 JavaScript 在线实例为什么学习 JavaScript? JavaScript 是 Web 的编程语言。 所有现代的 HTML 页面都可以使用 JavaScript。 JavaScript 非常容易学。 JavaScript 在线实例 <!DOCTYPE html> <html> <head> <meta charset"utf-8&q…

计算x的平方根x含负数和复数cmath.sqrt(x)

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 计算x的平方根 x含负数和复数 cmath.sqrt(x) cmath.sqrt(-4)输出的结果是&#xff1f; import cmath import math a 4 print("【显示】a ",a) print("【执行】math.sqrt(a)&…

深入探索Pandas:读写JSON文件的终极指南与实战技巧read_json、to_json【第80篇—读写JSON文件】

深入探索Pandas&#xff1a;读写JSON文件的终极指南与实战技巧read_json、to_json 在数据分析和处理过程中&#xff0c;JSON&#xff08;JavaScript Object Notation&#xff09;是一种常见的数据格式。Pandas库提供了方便而强大的工具&#xff0c;使得读取和写入JSON文件变得…

SpringCloud第二天

1.Nacos配置管理 Nacos除了可以做注册中心&#xff0c;同样可以做配置管理来使用。 1.1.统一配置管理 当微服务部署的实例越来越多&#xff0c;达到数十、数百时&#xff0c;逐个修改微服务配置就会让人抓狂&#xff0c;而且很容易出错。我们需要一种统一配置管理方案&#x…

leetcode(双指针)15.三数之和(C++详细解释)DAY10

文章目录 1.题目示例提示 2.解答思路3.实现代码结果 4.总结 1.题目 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的…

车载诊断协议DoIP系列 —— 车载以太网诊断需求规范(网关、路由)

车载诊断协议DoIP系列 —— 车载以太网诊断需求规范(网关、路由) 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自…

C# CAD SelectionFilter下TypedValue数组

SelectionFilter是用于过滤AutoCAD实体的类&#xff0c;在AutoCAD中&#xff0c;可以使用它来选择具有特定属性的实体。构造SelectionFilter对象时&#xff0c;需要传入一个TypedValue数组&#xff0c;它用于定义选择规则。 在TypedValue数组中&#xff0c;每个元素表示一个选…