非递归求解汉诺塔的两种方法

如同我们能求出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;
}

猜你喜欢

转载自blog.csdn.net/gjg666/article/details/78489304