7-17 汉诺塔的非递归实现 (25分)
一开始看见通过了0.4+,以为是送分题,结果我错了。
花了好长时间看博客没搞懂怎么非递归实现(菜……)。
后面看了
https://blog.csdn.net/computerme/article/details/18080511的算法和https://zhuanlan.zhihu.com/p/36085324的非递归实现方法受到启发才把代码给敲出来。
下面简单说一下我理解到的方法吧!
第一步是判断输入的n是奇数还是偶数,若为奇数,则按顺时针以ACB的顺序摆成品字型,若为偶数,则按顺时针以ABC的顺序摆成品字型。(参考下图)
第二步将序号为1(最小)的盘,按顺时针放到下一个字母。假如以ABC顺序顺时针摆放时,若1盘在A,则将它移动到B,若在B则移动到C……
第三步处理剩余两个字母(1盘此时不在的那两个字母),暂且称为X和Y。可能会出现如下几种情况:
1)X和Y都有盘,此时他们顶层的盘必能比较出大小,那么这时候将小的盘移动到大的盘上面,结束。
2)X和Y都没盘,不用做任何处理。(比如输入的n为1的时候就会出现这种情况)
3)X和Y一个有盘,一个没盘,这时候将有盘的一边的盘移动到没有盘的一边,结束。
只要我们循环运行第二步和第三步就能完成汉诺塔的非递归实现啦。
下面是我的AC代码:
//非递归AC代码 //用cout最后一个测试会超时,改为printf就AC了 4 #include <iostream> #include <string> #include <cstring> #include <stdio.h> #include <cmath> using namespace std; int n;//输入的盘子数 class stack_//“三根柱子”的类型 { public: stack_() :r(0){} ~stack_() {} void push(int k) { h[++r] = k; } int pop() { if (r <= 0) return 0; else return h[r--]; } public: char name; int r;//指针 int h[10000]; }; stack_ S[3];//将三根柱子定义为数组,方便操作 bool is_over(int cnt)//判断移动次数有没有超过2^n-1 { if (cnt >= pow(2, n)-1) return true; else return false; } void move(char a, char b, char c)//移动函数主体 { int pop_x,temp1,temp2; int cnt = 0;//累计移动的次数 int i = 0;//循环的次数 while(cnt < pow(2,n)-1 )//移动次数到达最大时就要退出循环,继续循环会导致错误 { int k;//中间变量,简化式子 pop_x = S[i % 3].pop();//随着i的变化,可以实现对S[0],S[1],S[2]轮回判断 if (pop_x == 1) { k = i + 1; S[k % 3].push(1); if (!is_over(cnt)) { //cout << S[i % 3].name << " -> " << S[k% 3].name << endl; printf("%c -> %c\n", S[i % 3].name, S[k % 3].name); cnt++; } temp1 = S[(k + 1) % 3].pop(); temp2 = S[(k - 1) % 3].pop(); if ((temp1 != 0 && temp2 != 0)&& (temp1 < temp2) || temp2 == 0 and temp1 != 0)//temp1 移动到 temp2 的情况 { S[(k - 1) % 3].push(temp2); S[(k - 1) % 3].push(temp1); if (!is_over(cnt)) { //cout << S[(k + 1) % 3].name << " -> " << S[(k - 1) % 3].name <<endl; printf("%c -> %c\n", S[(k+1) % 3].name, S[(k-1) % 3].name); cnt++; } } else if (temp1 == 0 && temp2 == 0) { //不移动任何盘子,只要把刚刚出栈的元素重新压回去 S[(k + 1) % 3].push(temp1); S[(k - 1) % 3].push(temp2); } else { S[(k + 1) % 3].push(temp1); S[(k + 1) % 3].push(temp2); if (!is_over(cnt)) { //cout << S[(k - 1) % 3].name << " -> " << S[(k + 1) % 3].name << endl; printf("%c -> %c\n", S[(k-1)% 3].name, S[(k+1) % 3].name); cnt++; } } i++;//注意在末尾将i的值加1,实现0,1,2的轮回 } else { S[i % 3].push(pop_x);//不符合条件,重新压回栈 i++; } } //cout << endl << cnt << endl; } void hanoi(int n, char a, char b, char c)//接口 { S[0].name = a, S[1].name = b; S[2].name = c; for (int i = n; i >= 1; i--)//从大到小将盘子压入栈 { S[0].push(i); } move(a, b, c);//调用move开始进行移动 } int main() { cin >> n; if (n % 2 == 0) hanoi(n, ‘a‘, ‘b‘, ‘c‘);//偶数的时候按abc顺序 else hanoi(n, ‘a‘, ‘c‘, ‘b‘);//奇数的时候按acb顺序 return 0 ; }
再附加个递归实现的:
//递归实现 #include <iostream> #include <string> #include <cstring> using namespace std; void hanoi(int n, char a, char b, char c) { if(n==1) { cout << a <<" -> " << c << endl; return; } else { hanoi(n-1,a,c,b); cout << a <<" -> " << c <<endl; hanoi(n-1,b,a,c); return; } } int main() { int n ; cin >> n; hanoi(n,‘a‘,‘b‘,‘c‘); return 0; }
对于递归算法我的个人理解:
首先将初始盘子看成是n-1的整体和最下面的最大一块的组合体。
hanoi(n-1,a,c,b); cout << a <<" -> " << c <<endl; hanoi(n-1,b,a,c);
hanoi(n,A,B,C,)中A为初始位置,C为目标位置,上述代码第一步目的是将n-1整体从A移动到B,所以调用hanoi(n-1,A,C,B)。依此类推,第二步是要把最大一块从A移动到C,所以也可写成hanoi(1,A,B,C),第三步同理。
那么为什么hanoi(n-1,A,B,C)可以实现将上面的n-1个盘从A移动到B呢?
我的理解:
当n=2时,即n-1为1的时候,这个函数显然是可以实现这一目标的。当n=3时n-1就为2了,这个时候调用hanoi(n-1,A,B,C)其实就是调用hanoi(2,A,B,C),那么我们刚刚已经确定参数为2的时候是可以达到目的的,那么可以推出,n为3的时候也可以达到目的(因为他是借助n-1=2时的函数实现的),于是就可以继续往后面推断出n为任何数字的时候都可以实现这一功能。