一、问题描述
汉诺塔问题是一个经典的问题。汉诺塔(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;
}