汉诺塔的非递归实现 (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
我的想法
这道题目……我靠我靠我靠,让我彻底的感受到了CPP和C的取舍。
大佬们都是用cpp的库,然后用c的读取和输出。而我呢,自以为是个小佬,还比较懒,就很喜欢cin和cout。iostream真是个“好东西”,让我做这道题目做了八个多小时。
第一次做
我第一次,看到这个非递归的题目,想先拿递归碰碰运气,看看能不能过。结果,正如我所料,出题人应该恰到好处的卡一下,递归就是过不了,只能非递归。所以我只是敲了一下敲着玩玩,看看能不能照着递归把它翻译成循环,但发现这种递归翻译起来没那么简单……
(实际上,后面这道题目递归过了……因为仅仅把cout改成了printf……)
第二次做
我用的三个栈模拟三个柱子,就真的是傻瓜式模拟,后来写的循环越来越多,实在是没什么意思,也不合理,我直接就不写了,打算换个思路,于是打开了PPT,找规律,开始~
所以来到了第三种做法:
第三次做
我找到了这种规律:
其实,和递归的思路差不多,一推二,二退三,三推多,多推万物……
n=1,a->c,没有任何争议。
n=2,就是n=1所有的,将ab调换一下;加一个a-.>c,再把n=1所有的bc调换即可,以此类推。
我利用四个栈,写了一下这个模拟,其实是模拟两个栈,但是每个栈都是两部分,每一部分是上一个n的部分字母调换后的结果,所以将两部分用两个栈实现起来,再用一个string= a->c连接起来,比较方便管理,但是最后一格电还是超时。
(实际上,我把cout改成了printf试了一下,卧槽,过了……)
第四次做
实在没办法了……我觉得第三个优化的已经很不错了……于是去网上找了找思路。找到了这个,是一位美国学者概括的,太特么强了:
一个美国学者总结得到:所有的汉诺塔移动可以总结为重复的两步,我们假设现在最小的圆盘在a柱子上,柱子为a,b,c
第一步:将最小圆盘移动到下一个柱子上,也就是b
第二步:对a柱子和c柱子进行顶上最小的元素进行判断,把小一点的那个圆盘移动到大一点的那个圆盘(有空则摞在空柱子上)。
重复上述两步就可以得到答案。
注意:这样得到的最后的答案不一定是摞在c上,如果N是偶数将摞在b上,所以如果N是偶数我们就令第二个柱子为c,第三个柱子为b,这样就一定最后是摞在c上的。
我又打开了那个PPT试了一下……卧槽,竟然是真的……这特么怎么想到的……
于是很快的,我又敲了一组代码,但是,还是最后一个点超时!!!!!超时超时超时!!!!!
(实际上,我把cout改成printf,过了……)
总结
实际上,这道题目对我的帮助最大的就是又一次让我意识到了iostream和printf的差距。对于这种超大量数据输出输入的题目,用iostream无异于自己给自己挖坑。
当你用了cout,你就会:
而当你用了printf,你就会:
知道了没?所以别老是嫌弃printf的繁琐,人家多快啊~帮你冲上云霄,迅速得到快感,尽享AC高潮!
接下来粘三组代码,都是AC代码,我敲的第一种、第三种、第四种。
首先是递归实现的AC代码:
#include<iostream>
using namespace std;
void move(int n, char a, char b, char c){
if (n <= 0)
return;
if (n == 1){
printf("%c -> %c",a,c);
//cout << a << " -> " << c << endl;太特么慢了,没快感,不AC
}
else{
move(n - 1, a, c, b);
printf("%c -> %c",a,c);
//cout << a << " -> " << c << endl;太特么慢了,没快感,不AC
move(n - 1, b, a, c);
}
}
int main() {
int a;
cin >> a;
move(n, 'a', 'b', 'c');
system("pause");
return 0;
}
然后是第三种方法,四个栈模拟两个杯子,纯靠规律,将问题完全转化成一道模拟四个杯子倒来倒去的代码:
#include<iostream>
#include<stack>
#include<string>
using namespace std;
struct zu {
char zhu1, zhu2;
};
int main() {
zu z; z.zhu1 = 'a'; z.zhu2 = 'c';
stack<zu> s1[2], s2[2], temps[2];
zu changeBC[2]; changeBC[0].zhu1 = 'a'; changeBC[0].zhu2 = 'b';
changeBC[1]; changeBC[1].zhu1 = 'b'; changeBC[1].zhu2 = 'c';
zu addoneinthemiddle; addoneinthemiddle.zhu1 = 'a'; addoneinthemiddle.zhu2 = 'c';
string mid = " -> ";
int n; cin >> n;
if (n <= 0)return 0;
else if (n == 1)cout << "a -> c" << endl;
else {
for (int i = 2; i <= n; i++) {
//temps记录将要被清空的栈s1和s2
temps[0] = s1[0];
while (!s1[0].empty()) {
if (s1[0].top().zhu1 == 'b')s1[0].top().zhu1 = 'c';
else if (s1[0].top().zhu1 == 'c')s1[0].top().zhu1 = 'b';
if (s1[0].top().zhu2 == 'c')s1[0].top().zhu2 = 'b';
else if (s1[0].top().zhu2 == 'b')s1[0].top().zhu2 = 'c';
s2[0].push(s1[0].top());
s1[0].pop();
}
s2[0].push(changeBC[0]);
temps[1] = s1[1];
while (!s1[1].empty()) {
if (s1[1].top().zhu1 == 'b')s1[1].top().zhu1 = 'c';
else if (s1[1].top().zhu1 == 'c')s1[1].top().zhu1 = 'b';
if (s1[1].top().zhu2 == 'c')s1[1].top().zhu2 = 'b';
else if (s1[1].top().zhu2 == 'b')s1[1].top().zhu2 = 'c';
s2[0].push(s1[1].top());
s1[1].pop();
}
//已经将第一部分拷贝完成
while (!temps[0].empty()) {
if (temps[0].top().zhu1 == 'b')temps[0].top().zhu1 = 'a';
else if (temps[0].top().zhu1 == 'a')temps[0].top().zhu1 = 'b';
if (temps[0].top().zhu2 == 'a')temps[0].top().zhu2 = 'b';
else if (temps[0].top().zhu2 == 'b')temps[0].top().zhu2 = 'a';
s2[1].push(temps[0].top());
temps[0].pop();
}
s2[1].push(changeBC[1]);
while (!temps[1].empty()) {
if (temps[1].top().zhu1 == 'b')temps[1].top().zhu1 = 'a';
else if (temps[1].top().zhu1 == 'a')temps[1].top().zhu1 = 'b';
if (temps[1].top().zhu2 == 'a')temps[1].top().zhu2 = 'b';
else if (temps[1].top().zhu2 == 'b')temps[1].top().zhu2 = 'a';
s2[1].push(temps[1].top());
temps[1].pop();
}
//第二部分拷贝完成
for (int i = 0; i <= 1; i++) {
while (!s2[i].empty()) {
s1[i].push(s2[i].top());
s2[i].pop();
}
}
}
for (int i = 0; i < 2; i++) {
while (!s1[i].empty()) {
//cout << s1[i].top().zhu1 << mid << s1[i].top().zhu2 << endl;
printf("%c -> %c\n",s1[i].top().zhu1,s1[i].top().zhu2);
s1[i].pop();
}
if (i == 1)break;
cout <<"a -> c" << endl;
}
}
system("pause");
return 0;
}
第三种,那位美国学者大佬说的思路:
#include<iostream>
#include<stack>
using namespace std;
char name[3] = { 'a','b','c' };
stack<int> s[3];
bool change(int before, int after) {
if (s[before].empty())
return false;
if (!s[after].empty())
if ((s[after].top() - s[before].top()) < 0)
return false;
s[after].push(s[before].top());
s[before].pop();
printf("%c -> %c\n", name[before], name[after]);//卧槽不改这个就最后一个点超时!!!!
return true;
}
int main() {
int n; cin >> n;
if (n % 2 == 1) {
name[1] = 'c'; name[2] = 'b';
}
for (int i = n; i >= 1; i--) {
s[0].push(i);
}
int i = 0;
bool noend = true;
while (noend) {
i++;
change((i-1+3) % 3 , (i) % 3 );
if(!change((i - 1) % 3, (i + 1) % 3)&&!change((i + 1) % 3, (i - 1) % 3))
noend = false;
}
system("pause");
return 0;
}