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

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

有三根相邻的柱子,标号为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()函数的顺序。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
汉诺塔问题的非递归实现可以利用栈来实现。具体步骤如下: 1. 将初始状态下的所有盘子按从大到小的顺序压入起始栈。 2. 根据汉诺塔的规则,将起始栈中的最上面的盘子移动到目标栈中。如果某一时刻起始栈为空,则说明所有盘子都已经被移动到目标栈中,此时程序结束。 3. 将目标栈中的最上面的盘子移动到辅助栈中。 4. 重复步骤2和3,直到所有盘子都被移动到目标栈中为止。 下面是一个使用C++实现汉诺塔非递归代码示例: ```c++ #include <iostream> #include <stack> using namespace std; void hanoi(int n, stack<int>& from, stack<int>& to, stack<int>& aux) { int moves = pow(2, n) - 1; //总共需要移动的次数 for (int i = n; i >= 1; i--) { from.push(i); //初始状态下所有盘子都在起始栈中 } for (int i = 1; i <= moves; i++) { if (i % 3 == 1) { if (!aux.empty() && (from.empty() || aux.top() < from.top())) { //将辅助栈中的盘子移动到起始栈中 from.push(aux.top()); aux.pop(); cout << "Move disk " << from.top() << " from Aux to From" << endl; } else { //将起始栈中的盘子移动到目标栈中 to.push(from.top()); from.pop(); cout << "Move disk " << to.top() << " from From to To" << endl; } } else if (i % 3 == 2) { if (!to.empty() && (from.empty() || to.top() < from.top())) { //将目标栈中的盘子移动到起始栈中 from.push(to.top()); to.pop(); cout << "Move disk " << from.top() << " from To to From" << endl; } else { //将起始栈中的盘子移动到辅助栈中 aux.push(from.top()); from.pop(); cout << "Move disk " << aux.top() << " from From to Aux" << endl; } } else { if (!to.empty() && (aux.empty() || to.top() < aux.top())) { //将目标栈中的盘子移动到辅助栈中 aux.push(to.top()); to.pop(); cout << "Move disk " << aux.top() << " from To to Aux" << endl; } else { //将辅助栈中的盘子移动到目标栈中 to.push(aux.top()); aux.pop(); cout << "Move disk " << to.top() << " from Aux to To" << endl; } } } } int main() { stack<int> from, to, aux; int n = 3; //汉诺塔的层数 hanoi(n, from, to, aux); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值