如同我们能求出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;
#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;
void hanoi(int n)
{
int top[3]={n-1,-1,-1};
int p[3][n];
int from,to,flag1,s1,s2;
for(s1=0;s10][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;
}