汉诺塔的两种非递归解法

如同我们能求出fabonacci数列的表达式,一定能用归纳的办法解决hanoi问题。

1 基本规律:
        最容易看出的规律就是盘子移动的序列:
给盘子从小到大编号1,2,……,N,试验移动盘子则可以得到一个这样的盘子移动序列(可以叫他hanoi数列H(n)):
1,2,1, 3, 1,2,1,    4      ,1,2,1, 3, 1,2,1,………
容易归纳出,1占据所有的奇数位,2占据所有序号可被2整除而不能被4整除的位,。。。。。。m占了能被2^(m-1)整除而不能被2^m整除的所有的位置。。。。。。。。由此可得,对m=((p*2^k) + 2^k-1),H(m)=k。很容易用数学归纳法证明。
这样就可以随机得到某一步所移动的盘子了。

另一方面,还要确定某一个盘子是往哪而移动的。这一点不容易直接想到,但是通过实验可以发现,在某一个盘子数N下,1号盘子的移动方向总是一定的。方向一定就是说,总是按照1???或者1???的方向移动一步,进一步可以想到所有的盘子的移动方向总是一定的。通过实验还可以发现,奇数号盘子的移动方向都相同,同样偶数号盘子的移动方向也相同,而奇数和偶数号盘子的移动方向正好相反。
进一步可以发现盘子的移动方向是由盘子总数N决定的,N为偶数时奇数号的盘子向右移动,偶数号的向左移动;N为奇数时则相反。最容易的验证方法就是最大号的盘子,他们总是只移动一次,并且是从1号柱子移到3号柱子即向左移。可以用数学归纳法证明。
2 进一步分析
我们已经得到了盘子移动序列和每个盘子的移动方向,而每个盘子的起始状态都是相同的,在1号柱子上,这样就可以随机取得某一步移动的具体情况了。
对第m=((p*2^k) + 2^(k-1)),易知移动的盘子是第k号,而且这是序列中第p+1个k号盘子,也就是说此前k号盘子已经移动了p次,那么k号盘现在就在(1+p*(-1)^(N+k+1))%3号柱子上,他将被移动到(1+(p+1)*(-1)^(N+K+1))%3号柱子上。
于是,我们就可以随机得到第m步的情况
k号盘从第(1+p*(-1)^(N+k+1))%3号柱子移动到第(1+(p+1)* (-1)^(N+k+1))%3号柱子上。

3 代码实现
#include<stdio.h>;
#define N 5
move(int m)      //第m次移动,对应于移动序列1,2,1,3,1,2,1,4,1,2,1….的第m项
{
        int from,to;
        int p = m;
        int k = 1;
        for(k=1;p%2 != 1;k++)
                p /= 2;
        p /= 2;
        m=1;
        if((N+k+1)%2) m=-1;
        if((from=(1+p*m)%3) <=0)from +=3;
        if((to = (1+(p+1)*m)%3) <= 0) to +=3;
        printf("%d: %d-->;%d/n",k,from,to);
}

hanoi(int n)
{
        int s,m,i;
        s = 1;
        for(i=1;i<=n;i++)s *= 2;
        s -= 1;
        for(m=1; m<s; m++)
                move(m);
}
int main()
{
        hanoi(N);
        return 0;
}
这个算法的最有用之处是可以随机得到某一步的移法(move(int m)),时间复杂度为O(lg m),而递归或者用栈的话复杂度为O(m).

其实还有一种更适合于手动实验的算法。还是看那个盘子移动序列,从中可以看出,每隔一次移动一次-号盘,移动一号盘后的下一步操作完全可以有当时盘的排列决定,因为只能把小盘移到大盘上。
#include<stdio.h>;
void hanoi(int n)
{
        int top[3]={n-1,-1,-1};
        int p[3][n];
        int from,to,flag1,s1,s2;
        for(s1=0;s1<n;s1++)p[0][s1]=n-s1;//初始化,把盘子放在第一个柱子上
        int m = 1;                //一号盘的移动方向               
        if((n+2)%2)m = -1;
        flag1=0;                //一号盘的初始位置
       
               
        while((top[0]+top[1]) != -1)//最后一步之前1,2两个柱子必有一个为空top[]=-1
        {
                from = flag1;                                //移动一号盘
                if( (flag1=(flag1+m)%3)<0)flag1 +=3;
                to=flag1;
                printf("%d: %d-->;%d/n",1,from+1,to+1);
                p[to][++(top[to])]=1;
                top[from]--;

                if( (s1=flag1+1) >;= 3) s1 -= 3;
                if( (s2=flag1-1) < 0) s2 +=3;
                if(p[s1][top[s1]] < p[s2][top[s2]] || top[s2]==-1)        //移动其他盘
                {
                        from = s1;
                        to = s2;
                        printf("%d: %d-->;%d/n",p[from][top[from]],from+1,to+1);
                        p[to][++(top[to])]=p[from][top[from]];
                        top[from]--;
                }else
                {
                        from = s2;
                        to = s1;
                        printf("%d: %d-->;%d/n",p[from][top[from]],from+1,to+1);
                        p[to][++(top[to])]=p[from][top[from]];
                        top[from]--;               
                }
        }
        from = flag1;                                //移动一号盘
        if( (flag1=(flag1+m)%3)<0)flag1 +=3;
        to=flag1;
        printf("%d: %d-->;%d/n",1,from+1,to+1);
        p[to][++(top[to])]=1;
        top[from]++;
        flag1 = to;               
}
int main()
{
        hanoi(5);
        return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
汉诺塔问题的非递归实现可以借助堆栈以循环方式求解。具体实现方法如下: 1.将起始柱、目标柱、借助柱的状态压入栈中。 2.当栈不为空时,取出栈顶状态,如果当前状态为最终状态,则输出结果并结束程序。 3.否则,根据当前状态,计算出下一步的状态,并将其压入栈中。 4.重复步骤2和3,直到找到最终状态为止。 具体实现过程中,需要注意以下几点: 1.状态的表示可以使用一个结构体,包含起始柱、目标柱、借助柱的编号以及当前移动的盘子数。 2.计算下一步状态时,需要根据当前状态的盘子数和柱子的编号来确定下一步移动的盘子和目标柱。 3.在压入栈中时,需要按照一定的顺序将状态压入,以保证栈顶状态是当前最优状态。 下面是一个简单的C++实现代码,仅供参考: ``` #include <iostream> #include <stack> using namespace std; struct State { int n; // 当前移动的盘子数 int from, to, via; // 起始柱、目标柱、借助柱的编号 }; void move(int n, int from, int to) { cout << "Move disk " << n << " from " << from << " to " << to << endl; } void hanoi(int n, int from, int to, int via) { stack<State> s; s.push({n, from, to, via}); while (!s.empty()) { State cur = s.top(); s.pop(); if (cur.n == 1) { move(1, cur.from, cur.to); } else { s.push({cur.n - 1, cur.via, cur.to, cur.from}); s.push({1, cur.from, cur.to, cur.via}); s.push({cur.n - 1, cur.from, cur.via, cur.to}); } } } int main() { int n = 3; hanoi(n, 1, 3, 2); return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值