汉诺塔(必须经过中间柱子)递归与非递归详解与代码实现

来源:互联网 发布:mac开机开到一半黑屏 编辑:程序博客网 时间:2024/04/29 03:46

首先介绍一下汉诺塔最初始的规则:

有三根相邻的柱子,标号为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;}

测试:
盘数为1
盘数为2
当然,上述的代码只能实现将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()函数的顺序。

1 0