算法:四柱汉诺塔

四塔问题:设有A,B,C,D四个柱子(有时称塔),在A柱上有由小到大堆放的n个盘子,如图所示。

今将A柱上的盘子移动到D柱上去。可以利用B,C柱作为工作栈用,移动的规则如下:
①每次只能移动一个盘子。
②在移动的过程中,小盘子只能放到大盘子的上面。
设计并实现一个求解四塔问题的动态规划算法,并分析时间和空间复杂性。

算法思想:
用如下算法移动盘子(记为FourPegsHanoi):
1)、将A柱上n个盘子划分为上下两部分,下方部分共有k(1≤k≤n)个盘子,上方部分共有n - k个盘子。
2)、将A柱上面部分n–k个盘子使用FourPegsHanoi算法经过C、D柱移至B柱。
3)、将A柱剩余的k个盘子使用ThreePegsHanoi算法经过C柱移至D柱。
4)、将B柱上的n–k个盘子使用FourPegsHanoi算法经过A、C柱移至D柱。

ThreePegsHanoi算法如下(设三个柱子分别为A、B、C,A柱上共有k个盘子):
1)、将A柱上方k-1个盘子使用ThreePegsHanoi算法经过B柱移至C柱。
2)、将C柱上最后一个盘子直接移至C盘。
3)、将B柱上k-1个盘子使用ThreePegsHanoi算法经过A柱移至C柱。

算法步骤:
根据动态规划的四个步骤,求解如下:
1)、最优子结构性质:
四柱汉诺塔问题的最优解是用最少的移动次数将A柱上的盘子全部移到D柱上。当盘子总数为i时,我们不妨设使用FourPegsHanoi的最少移动次数为f(i)。相应的ThreePegsHanoi 算法移动次数为g(k),由于g(k)=2g(k-1)+1=2^k -1,当k确定时,g(k)也是不变的。
f(i)为最优解时,其子问题f(i-k)也必为最优解。如果f(i-k)不是最优解,那么存在f’(i-k) < f(i-k)。用f’(i-k)替换f(i-k)将产生一个比f(i)更优的解。这与f(i)为最优解是矛盾的。所以本问题具有最优子结构性质。

2)、递归地定义问题的最优解:
根据上述FourPegsHanoi算法得到最少移动次数f(i):
算法:四柱汉诺塔_第1张图片
通过这个表达式我们可以知道,k取那个值时f(i)的值,也就是说,不用具体操作,就可以知道移动的最少次数,并且知道k的值,所以在算法实现时,求出k的值是非常重要的。下面的代码就是用来求k的。

int k[100];
int moves[100][100];
void minMoves(int n) //求k 
{
        int min = -1;
        moves[0][0] = 0;
        k[0] = 0;
        for(int i = 1;i<= n;i++)
        {
                min = 1000000000;
                for(int j=1;j<=i;j++)
                {
                        moves[i][j] = 2*moves[i-j][k[i-j]] -1 + (2<<(j-1)) ;
                        if(moves[i][j] < min)
                        {
                                min = moves[i][j];
                                k[i] = j;
                        }
                }
        }
}

完整代码:(动态规划)

#include 
using namespace std;
int k[100];
int moves[100][100];
void minMoves(int n) //求k 
{
        int min = -1;
        moves[0][0] = 0;
        k[0] = 0;
        for(int i = 1;i<= n;i++)
        {
                min = 1000000000;
                for(int j=1;j<=i;j++)
                {
                        moves[i][j] = 2*moves[i-j][k[i-j]] -1 + (2<<(j-1)) ;
                        if(moves[i][j] < min)
                        {
                                min = moves[i][j];
                                k[i] = j;
                        }
                }
        }
}
void move(int a,int b)
{
	printf("%d --> %d\n",a,b);
}

void hanoi(int x,int a,int b,int c)
{
	if(x == 1)
		move(a,b);
	else
	{
		hanoi(x-1,a,c,b);
		move(a,b);
		hanoi(x-1,c,b,a);
	}
}

void fourhanoi(int x,int a,int b,int c,int d)
{
	if(x == 1)
		move(a,b);
	else
	{
		int tk = k[x];
		fourhanoi(x-tk,a,d,b,c);
		hanoi(tk,a,b,c);
		fourhanoi(x-tk,d,b,a,c);
	}
}

int main()
{
	minMoves(65);
//	for(int i=1; i<=64; i++)
//	{
//		printf("k[%d] = %d\n",i,k[i]);
//	}
	int n;
	
	cin >> n;

	fourhanoi(n,1,2,3,4);
	return 0;
}

下面我们来看看,1941年,美国的J. S. Frame,给出的四柱汉诺塔的算法思想,也叫Frame算法:

1、用4柱汉诺塔算法把A柱上部分的n- r个碟子通过C柱和D柱移到B柱上【F( n- r )步】。

2、用3柱汉诺塔经典算法把A柱上剩余的r个碟子通过C柱移到D柱上【2^r-1步】(参照上述三柱时的情况)。

3、用4柱汉诺塔算法把B柱上的n-r个碟子通过A柱和C柱移到D柱上【F(n-r)步】。

4、依据上边规则求出所有r(1≤r≤n)情况下步数f(n),取最小值得最终解。

因此Frame算法的递归方程如下:

F(n)=min(2*F(n-r)+2^r-1),(1≤r≤n)。

大家有没有发现,其实,这个算法思想跟我们之前认为合理的算法基本一致,差别只是在于他将我们的n-2个碟子缓存到B上,改为了将n- r个碟子转移到B柱上。

差别即使核心,这个算法的核心,就是计算n个盘子的情况下,r为何值时,能够使得算法最优。

找到了核心,我们现在的任务就明确了,就是对r值的计算。

这里给出了一个较笨的方法–枚举(不知道各位有没有其他方法)。就是将一定范围内的n与r的所有取值带入,得到满足F(n)为最小值的r的值,记为K[n] = r;
代码如下:

#define Max 100
#define maxx 1000000000
int k[Max+1] = {0};
void initk(void )
{
	int i, j;
	long long temp;
	long long m[Max+1] = {0};

	for( i = 1; i <= Max; i++ )
	{
		m[i] = maxx;
		for( j = 1; j <= i; j++ )
		{
			temp = 2*m[i-j] + (long long)pow(2,j) - 1;
			if( temp < m[i] )
			{
				m[i] = temp;
				k[i] = j;
//				printf("K[%d] = %d, m[i] = %d\n", i, k, temp );
			}
		}
	}
}

完整代码:(非动态规划)

#include 
using namespace std;
#define Max 100
#define maxx 1000000000
int k[Max+1] = {0};
void initk(void )
{
	int i, j;
	long long temp;
	long long m[Max+1] = {0};

	for( i = 1; i <= Max; i++ )
	{
		m[i] = maxx;
		for( j = 1; j <= i; j++ )
		{
			temp = 2*m[i-j] + (long long)pow(2,j) - 1;
			if( temp < m[i] )
			{
				m[i] = temp;
				k[i] = j;
//				printf("K[%d] = %d, m[i] = %d\n", i, k, temp );
			}
		}
	}
}
void move(int a,int b)
{
	printf("%d --> %d\n",a,b);
}

void hanoi(int x,int a,int b,int c)
{
	if(x == 1)
		move(a,b);
	else
	{
		hanoi(x-1,a,c,b);
		move(a,b);
		hanoi(x-1,c,b,a);
	}
}

void fourhanoi(int x,int a,int b,int c,int d)
{
	if(x == 1)
		move(a,b);
	else
	{
		int tk = k[x];
		fourhanoi(x-tk,a,d,b,c);
		hanoi(tk,a,b,c);
		fourhanoi(x-tk,d,b,a,c);
	}
}

int main()
{
	initk();
//	for(int i=1; i<=64; i++)
//	{
//		printf("k[%d] = %d\n",i,k[i]);
//	}
	int n;
	
	cin >> n;

	fourhanoi(n,1,2,3,4);
	return 0;
}

运行效果:
算法:四柱汉诺塔_第2张图片

你可能感兴趣的:(递归,C++,算法,算法,递归,汉诺塔)