【算法】汉诺塔

1.问题描述


有三根杆子(tower)A,B,C。A杆上有N个穿孔圆盘(disk),盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至C杆:

(1)每次只能移动一个圆盘;

(2)大盘不能叠在小盘上面。


2.递归解


用hanoi[n]记录n个盘从一个杆移到另一个杆的移动次数,移盘的流程:

(1)有算法可以将A杆上面n-1个盘移到B杆上,移动次数为hanoi[n-1]
(2)将最下面的盘(即最大的盘)移到C杆上,移动次数为1
(3)用该算法将B杆上的n-1个盘移到C杆上,移动次数为hanoi[n-1]

易得递推公式:hanoi[n]=2*hanoi[n-1]+1

源代码(C):

/*hanoi.h*/

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

extern int cnt;

void move(int n,char a,char b,char c);
/*main.c*/

#include "hanoi.h"

int cnt=0;

int main()
{
	int n;
	printf("%s","input the number of tower:");
	scanf("%d",&n);
	assert(n>0);
	move(n,'A','B','C');
	return 0;
}
/*move.c*/

#include "hanoi.h"

void move(int n,char a,char b,char c)
{
	if(n==1)
	{
		++cnt;
		printf("%s%2d:%s%d%s%c%s%c.\n","step ",cnt," move disk ",1," from tower ",a," to tower ",c);
	}
	else
	{
		move(n-1,a,c,b);
		++cnt;
		printf("%s%2d:%s%d%s%c%s%c.\n","step ",cnt," move disk ",n," from tower ",a," to tower ",c);
		move(n-1,b,a,c);
	}
}

4.问题变种


4.1 POJ 1958


现在有4个tower和n个disk,求从tower A移到tower D所需的最少步骤数。

Discuss中给出了递推公式,自己很容易可以推出来:



其中,hanoi3[ i ]表示有3个tower时i个disk的移动次数,hanoi4[ i ]表示有4个tower时i个disk的移动次数。

源代码:
1958 Accepted 156K 0MS C 448B 2013-08-30 10:16:20
#include "stdio.h"
#define N 13

int main()
{
	int i,k;
	int hanoi3[N],hanoi4[N];
	
	/*initialization*/
	hanoi3[1]=1;
	hanoi4[1]=1;

	/*compute hanoi3*/
	for(i=2;i<N;i++)
		hanoi3[i]=2*hanoi3[i-1]+1;
    printf("%d\n",hanoi4[1]);

	for(i=2;i<N;i++)
	{
		hanoi4[i]=2+hanoi3[i-1];
		for(k=1;k<i;k++)
			if(hanoi4[i]>2*hanoi4[k]+hanoi3[i-k])
				hanoi4[i]=2*hanoi4[k]+hanoi3[i-k];
		printf("%d\n",hanoi4[i]);	
	}
	return 0;
}

4.2 POJ 3601


跟原始的汉诺塔问题所不同的是盘的大小有相同的,并且盘移到C杆之后应保持其在A杆的顺序。

用i-block表示大小为i的盘所组成的block(可以叫块,或组);a[i]表示i-block的盘的个数(即大小为i的盘的个数),b[i]表示将i个block不考虑顺序地从一个杆移到另一个杆的移动次数,所谓不考虑顺序,是指在每个block中不考虑盘的叠放次序;c[i]表示将i个block考虑顺序地从一个杆移到另一个杆的移动次数,所谓考虑顺序,是指在移入杆的所有盘要保持在移出杆的顺序。

设有i种盘,即block数为i,移盘的操作流程:
(1)将(i-1)-block, (i-2)-block, ... , 1-block的i-1个block不考虑顺序地从A杆移到C杆,移动次数为b[i-1]。
(2)将i-block从A杆移到B杆,移动次数为a[i];注意,i-block在B杆的顺序正好是在A杆的倒序,意思是说,如果i-block在A杆从上至下的顺序是1, 2, ... , a[i] ,则i-block在B杆的从上至下的顺序是a[i], a[i]-1, ... ,1。
(3)将i-1个block从C杆移回A杆,移动次数为b[i-1];注意,移回到A杆i-1个block保持了在(1)步骤中在A杆的初始顺序
(4)i-block从B杆移到C杆,移动次数为a[i];注意,i-block在C杆恢复在A杆的顺序(在纸上画一画就明白了)。
(5)将i-1个block从A杆移到C杆,移动次数为c[i-1]。

在不考虑顺序移动时,可以(1)将i-1个block从A杆移到B杆,移动次数为b[i-1];(2)将i-block从A杆移到C杆,移动次数为a[i];(3)将i-1个block从B杆移到C杆,移动次数为b[i-1]

从以上流程,得出递推公式:
b[i]=2*b[i-1]+a[i]
c[i]=2*b[i-1]+2*a[i]+c[i-1]

特别地,当a[i]==1时,即移动i-block不需要考虑顺序,有c[i]=b[i]。初始化,b[1]=a[1],c[1]=2*(a[1]-1)+1。

源代码:
3601 Accepted 164K 0MS C 392B 2013-08-30 17:59:16
#include "stdio.h"

#define MAX 101

int main()
{
	int i,n,m;
	int a[MAX],b[MAX],c[MAX];
	while(~scanf("%d%d",&n,&m))
	{
		for(i=1;i<=n;i++)
			scanf("%d",&a[i]);
		
		b[1]=a[1];
		c[1]=2*(a[1]-1)+1;

		for(i=2;i<=n;i++)
		{
			b[i]=(2*b[i-1]+a[i])%m;
			if(a[i]==1)
				c[i]=b[i];
			else
				c[i]=(2*b[i-1]+2*a[i]+c[i-1])%m;
		}
		printf("%d\n",c[n]);
	}
	return 0;
}


你可能感兴趣的:(【算法】汉诺塔)