首先介绍一下汉诺塔最初始的规则:
有三根相邻的柱子,标号为A,B,C,A柱子从上到下按照金字塔状叠放着n个不同大小的圆盘,现在把所有的盘子一个一个移动到柱子B上,并且每次移动同一根柱子上都不能出现大盘子在小盘子上方。
这是最初始的规则,实现的思路可以分为两个步骤:
(假设圆盘期初都在左边的柱子上,想移动到右边的柱子上)
1.如果只有一个圆盘,直接把左边的圆盘移动到右边。
2.如果有n个圆盘(n>1),先把1—n–1圆盘移动到中间的柱子上,最后一个圆盘进行第一个步骤,之后再把1—n-1圆盘从中间移动到右边。
给出一个博文的链接:打开链接,该博主写得简单易懂,十分推荐。
升级规则:不能将圆盘直接从左边移动到右边,必须经过中间的柱子
递归法:
其实思路与升级前的递归是一样的:
1.当只有一个圆盘时,先从左移到中间,再从中间移动到右边。
2.当有两个圆盘时,先把1(最上面的圆盘)从左边移到中间,再把1移动到右边。把2(1圆盘下面的圆盘)从左边移动到中间,然后把1从右边移动到中间,再把1移动到左边,把2从中间移动到右边,把1从左边移动到中间,再移动到右边。
3.……
总结来说我们要做的有两个步骤:
1、当只要一个圆盘时:
1)如果起点或者终点中有一个为中间柱子,直接把圆盘从起始柱子移动到目标柱子。
2)如果起点和终点都不为中间的柱子,则需要两步,首先把圆盘从起始柱子移动到中间柱子,在从中间柱子移动到目标柱子。
2、当有n(n>1)个圆盘时,需要把1—n-1圆盘从左边移动到右边,再将最底下的圆盘n移动到中间,把1—n-1圆盘从右边移动到左边,把圆盘n从中间移动到右边,1—n-1圆盘从左边移动到右边。
代码:
//hannuota II 递归
#include<iostream>
#include<vector>
#include<string>
using namespace std;
/*变量含义
* n:圆盘数量
*left:左柱子
*mid:中间柱子
*right:右柱子
*from:起点柱子
*to:目标柱子
*/
long hannuota(int n,string left,string mid,string right,string from,string to)
{
if(n == 1)
{
if(from == mid||to == mid)
{
cout<<"将"<<n<<"从"<<from<<"移动到"<<to<<endl;
return 1;
}
else
{
cout<<"将"<<n<<"从"<<from<<"移动到"<<mid<<endl;
cout<<"将"<<n<<"从"<<mid<<"移动到"<<to<<endl;
return 2;
}
}
else
{
long num1 = hannuota(n-1,left,mid,right,from,to);
cout<<"将"<<n<<"从"<<from<<"移动到"<<mid<<endl;
long num2 = hannuota(n-1,left,mid,right,to,from);
cout<<"将"<<n<<"从"<<mid<<"移动到"<<to<<endl;
long num3 = hannuota(n-1,left,mid,right,from,to);
return num1+num2+num3+2;
}
}
int main()
{
int n;
cout<<"盘数:";
cin>>n;
string from = "left";
string depend = "mid";
string to = "right";
if(n>=1)
{
cout<<"步数"<<hannuota(n,from,depend,to,from,to)<<endl;
}
return 0;
}
测试:
当然,上述的代码只能实现将n个圆盘从左边全部移到右边,或者右边移到左边,如果起点或者终点为中间圆盘,即会出错。但是实现全部只需要再加一种情况就好了。
(全面考虑)如果起点或者终点中有一个为中间柱子:
只剩一个圆盘时,直接移动到目标柱子,上面的代码已经包含,
不止一个圆盘时,需要把1—n-1圆盘移动到过渡柱子上(既不是起始柱子,也不是终点柱子),然后把圆盘n从起点移到终点,在把1—n-1从过渡移到终点。
更改 hannuota()函数
long hannuota(int n,string left,string mid,string right,string from,string to)
{
if(n == 1)
{
if(from == mid||to == mid)
{
cout<<"将"<<n<<"从"<<from<<"移动到"<<to<<endl;
return 1;
}
else
{
cout<<"将"<<n<<"从"<<from<<"移动到"<<mid<<endl;
cout<<"将"<<n<<"从"<<mid<<"移动到"<<to<<endl;
return 2;
}
}
else
{
if(from == mid||to == mid)
{
string depend = (from == left||to == left)?right:left;
long num1 = hannuota(n-1,left,mid,right,from,depend);
cout<<"将"<<n<<"从"<<from<<"移动到"<<to<<endl;
long num2 = hannuota(n-1,left,mid,right,depend,to);
return num1+num2+1;
}
else
{
long num1 = hannuota(n-1,left,mid,right,from,to);
cout<<"将"<<n<<"从"<<from<<"移动到"<<mid<<endl;
long num2 = hannuota(n-1,left,mid,right,to,from);
cout<<"将"<<n<<"从"<<mid<<"移动到"<<to<<endl;
long num3 = hannuota(n-1,left,mid,right,from,to);
return num1+num2+num3+2;
}
}
}
至此,递归方法已经说明
非递归:
借助栈实现,构建3个栈,分别代表3个柱子。整个汉诺塔的步骤其实只有4步,从左移到中间,从中间移到左边,从中间移到右边,从右边移到中间。而且这四步中满足两个条件:
1、相邻不可逆:意思就是我执行了“从中间移到左边”,下一步就不行执行“从左边移到中间”,不然之前的步骤就没有意义了,求出来的也不是最短的步数。
2、 小压大原则:压入的圆盘必须比柱子上最上面的圆盘小,否则不行。
而且每次选择时,可以根据这两个条件,排除4步中其他3步,只有1个满足两个条件,因此就选择那种方法移动。证明:
假设上一步是“从左到中间”,则根据相邻不可逆排除“从中间到左“,而且根据小压大,“从中间到 右”和“从右到中间”只有一个符合,因此下一步是可以根据这两个条件来唯一确定的。
代码实现:
//hanbuota II 非递归
#include<iostream>
#include<vector>
#include<string>
#include<stack>
using namespace std;
enum Action{
NO,LTOM,MTOL,RTOM,MTOR
};
int fstackTotstack(Action &a,Action preAction,Action nowAction,stack<int>&fstack,stack<int >&tstack,string from,string to)
{
if(preAction != a&&fstack.size()!=0&&(tstack.size()==0||fstack.top()<tstack.top()))
{
tstack.push(fstack.top());
cout<<"将"<<fstack.top()<<"从"<<from<<"移动到"<<to<<endl;
fstack.pop();
a = nowAction;
return 1;
}
return 0;
}
int hannuota(int n,string left,string mid,string right)
{
stack<int>fstack;
stack<int>mstack;
stack<int>tstack;
for(int i = n;i>=1;i--)
fstack.push(i);
int step = 0;
Action a = NO;
while(tstack.size()!=n)
{
step += fstackTotstack(a,MTOL,LTOM,fstack,mstack,left,mid);
step += fstackTotstack(a,LTOM,MTOL,mstack,fstack,mid,left);
step += fstackTotstack(a,RTOM,MTOR,mstack,tstack,mid,right);
step += fstackTotstack(a,MTOR,RTOM,tstack,mstack,right,mid);
}
return step;
}
int main()
{
int n;
string left = "left";
string mid = "mid";
string right = "right";
while(cin>>n)
{
if(n>=1)
cout<<"步数"<<hannuota(n,left,mid,right)<<endl;
}
return 0;
}
测试:
注意:当起始圆盘不在左边柱子上时,需要更改四个fstackTotstack()函数的顺序。