汉诺塔问题是一种经典的问题。常见的处理方法是使用递归策略来处理。下面我们来介绍汉诺塔问题,及其两种变体。
1. 汉诺塔 I
1.1 问题描述
有一座塔A,上有64个碟。所有碟子按从大到小的次序从塔底堆放至塔顶。紧挨着塔A有另外两个塔B和塔C。
问题是:如何借助塔B,将塔A上的碟子移动到塔C上去,每次只能移动一个碟子,任何时候都不能把一个碟子放在比它小的碟子上面。
现在有N个圆盘,至少进行多少次移动才能把这些圆盘从最左边移动到最右边。
1.2 解决方案
采用递归策略解决问题,若从第一根杆上移动N个圆盘到第三根杆上需要F[N]次移动,那么F[N]的组成方式如下:
1. 先移动N-1个圆盘到第二根杆上需要F[N-1]次 移动
2. 将最大的圆盘移动到第三根杆上需要1次移动
3. 最后移动N-1个圆盘到第三根杆上需要F[N-1]次 移动
综上所述,有F[N] = 2 * F[N-1] + 1
1.3 代码
//汉诺塔问题
/*
有一座塔A,上有64个碟。所有碟子按从大到小的次序从塔底堆放至塔顶。紧挨着塔A有另外两个塔B和塔C。
问题是:如何借助塔B,将塔A上的碟子移动到塔C上去,每次只能移动一个碟子,任何时候都不能把一个碟子放在比它小的碟子上面。
现在有N个圆盘,至少进行多少次移动才能把这些圆盘从最左边移动到最右边。
*/
#include<iostream>
#include<cstdio>
using namespace std;
long long Function(int n)
{
if (n == 1) return 1;
else
{
return 2 * Function(n - 1) + 1;
}
}
int main()
{
int n;
while (scanf("%d", &n) != EOF)
{
if (n == 0) break;
cout << "Move : " << Function(n) << endl;
}
return 0;
}
2. 汉诺塔 II
2.1 问题描述
有一座塔A,上有64个碟。所有碟子按从大到小的次序从塔底堆放至塔顶。紧挨着塔A有另外两个塔B和塔C。
问题是:如何借助塔B,将塔A上的碟子移动到塔C上去,每次只能移动一个碟子,任何时候都不能把一个碟子放在比它小的碟子上面。
现在引入第4座塔D,上述规则保持不变。若有N个圆盘,至少进行多少次移动才能把这些圆盘从A移动到C?
2.2 解决方案
这是一个多柱汉诺塔问题,若总共移动i个碟子,具体步骤如下:
1. 将j个盘从a柱移动到c柱(借助b,d柱),所需步数为F[j]
2. 将a柱剩下的(i-j)个盘移动到d柱(借助b柱),是最基础的汉诺塔问题,可证明所需步数为2^(i-j)-1
3. 将c柱上的j个盘移动到d柱(借助a,b柱),所需步数为F[j]
综上所述,有F[i] = 2 * F[j] + 2^(i-j)-1.
显然上述结果随着j变化,若求最小的步数,则答案应为F[i] = min( 2 * F[j] + 2^(i-j)-1 ) (0<j<i),我们可以采用for循环来实现
2.3 代码
//汉诺塔问题 II
/*
有一座塔A,上有64个碟。所有碟子按从大到小的次序从塔底堆放至塔顶。紧挨着塔A有另外两个塔B和塔C。
问题是:如何借助塔B,将塔A上的碟子移动到塔C上去,每次只能移动一个碟子,任何时候都不能把一个碟子放在比它小的碟子上面。
现在引入第4座塔D,上述规则保持不变。若有N个圆盘,至少进行多少次移动才能把这些圆盘从A移动到C?
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = INT_MAX;
void Function(long long data[])
{
data[1] = 1;
data[2] = 3;
long long min, number;
for (int i = 3; i < 65; i++)
{
min = N;
for (int j = 1; j < i; j++)
{
number = 2 * data[j] + (int)pow(2, i - j) - 1;
if (number < min)
min = number;
}
data[i] = min;
}
return;
}
int main()
{
int n;
long long data[65];
Function(data);
while (scanf("%d", &n) != EOF)
{
if (n == 0) break;
cout << "Move : " << data[n] << endl;
}
return 0;
}
3. 汉诺塔 III
3.1 问题描述
有一座塔A,上有64个碟。所有碟子按从大到小的次序从塔底堆放至塔顶。紧挨着塔A有另外两个塔B和塔C。
问题是:如何借助塔B,将塔A上的碟子移动到塔C上去,每次只能移动一个碟子,任何时候都不能把一个碟子放在比它小的碟子上面。
现改变规则:不允许直接从最左边移动到最右边(每次移动一定是移到中间杆或从中间杆移出),也不允许大圆盘放到小圆盘的上面。
若有N个圆盘,至少进行多少次移动才能把这些圆盘从A移动到C?
3.2 解决方案
采用递归策略解决问题,若从第一根杆上移动N个圆盘到第三根杆上需要F[N]次移动,那么F[N]的组成方式如下:
1. 先移动N-1个圆盘到第二根杆上需要F[N-1]次 移动
2. 将最大的圆盘移动到中间杆上需要1次移动
3. 再移回N-1个圆盘到第一根杆上需要F[N-1]次 移动
4. 将最大的圆盘移动到最后一根杆上需要1次移动
5. 最后移动N-1个圆盘到第三根杆上需要F[N-1]次 移动
综上所述,有F[N] = 3 * F[N-1] + 2
3.3 代码
//汉诺塔问题 III
/*
有一座塔A,上有64个碟。所有碟子按从大到小的次序从塔底堆放至塔顶。紧挨着塔A有另外两个塔B和塔C。
问题是:如何借助塔B,将塔A上的碟子移动到塔C上去,每次只能移动一个碟子,任何时候都不能把一个碟子放在比它小的碟子上面。
现改变规则:不允许直接从最左边移动到最右边(每次移动一定是移到中间杆或从中间杆移出),也不允许大圆盘放到小圆盘的上面。
若有N个圆盘,至少进行多少次移动才能把这些圆盘从A移动到C?
*/
#include<iostream>
#include<cstdio>
using namespace std;
long long Function(int n)
{
if (n == 1) return 2;
else
{
return 3 * Function(n - 1) + 2;
}
}
int main()
{
int n;
while (scanf("%d", &n) != EOF)
{
if (n == 0) break;
cout << "Move : " << Function(n) << endl;
}
return 0;
}
4. 总结
上述问题,对于借助递归策略解决问题是一个很好的锻炼,以后也要时常回顾。