一点也不坑爹的汉诺塔(第八周编程题作业之一)

15 篇文章 0 订阅

汉诺塔的非递归实现 (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;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值