汉诺塔问题的一个变种
2014-11-18 17:38
190 查看
汉诺塔问题的一个变种
最近碰到的一个有意思的算法题。
问题定义
考虑简化的汉诺塔问题:有三个柱子1、2、3,每个柱子上只要最下面的盘子是最大的,其他盘子可以以任意顺序摆放。用尽量少的步骤将n个按顺序摆放的盘子从柱子1移到柱子3(仍为顺序摆放)。
问题分析
为了将所有盘子放到3上,需要三步:将前n-1个盘子以某种顺序放到2上;
将第n个盘子放到3上;
将2上的盘子放到3上。
其中步骤1与步骤3的差别在于:步骤1中盘子是第一次从柱子1上移出,而且移出之前是排好序的;步骤3中盘子经过步骤1的摆放可能是乱序的。
下面仔细分析步骤1。假设我们现在要将盘子i从1移动到2,这时柱子2、3上没有比i更大的盘子(因为只有前i-1个被移出了),那么柱子2必须是空的。因此有递归表示:
f(将前i个盘子移动到2) = f(将前i-1个盘子移动到3) + (将i移到2) + g(把前i-1个盘子从3移到2)
其中f在移动过程中必须考虑操作的合法性,g则不需要,所以g可以达到步数下限i-1(直接挨个移动)。这时前面的递推式可以写为
f(将前i个盘子移动到2) = f(将前i-1个盘子移动到3) + 1 + i-1 = f(将前i-1个盘子移动到3) + i
由此推出f(将前n个盘子移动到2)的步数下限为:
n * (n + 1) / 2
以n=7为例,根据这种操作方式将前6个盘子移动到2,其排列方式为
| 7 | 6 4 2 1 3 5 |
到现在为止,我们找到了完成步骤1的最优方法,但还不能确认通过这种方法能够达到全局最优。
现在仔细分析步骤3。经过前两个步骤之后,我们需要将2上的n-1个盘子按顺序放到3上。这时n-1一定被压在最下面,为了将n-1移出,我们需要将2上的n-2个盘子移动到1上。其递归表示为:
h(将n个盘子从2移动到3) = k(将n-1个盘子从2移动到1) + (将n移到3) + h(将n-1个盘子从1移动到3)
其中k需要移动n-1个盘子,其操作步数的下限为n-1。那么这个下限何时能达到呢?显然是当n-1个盘子中最大的n-1正好在最上面的时候(这样可以先把它放到最下面)。即只要柱2上的盘子排列成如下形式就能达到步数下限:
|n n-2 n-4 ... n-3 n-1
我们发现这正好是完成步骤1后形成的状态,我们恰好可以同时在步骤1和步骤3中达到最优解。
这时步骤3的递推表达式成为:
h(将n个盘子从2移动到3上) = k(将n-1个盘子从2移动到1上) + 1 + n-1 = n + h(将n-1个盘子从1移动到3上)
由此推出h(将n个盘子从2移动到3上)的步骤下限为n * (n + 1) / 2。
综合以上,我们整个过程中需要步骤数位:
f(将前n-1个盘子移动到2) + (将第n个盘子放到3上) + h(将n-1个盘子从2移动到3) = n(n-1) + 1
编程实现
有两种实现方式:递归和非递归,其中递归方式的思路与上文所述完全一致。不过如果搞清楚了整个过程,用循环的方式写起来更简单而且更快。递归方式代码:
#include <cstdio> void mix(int m, int a) { int i; if(m>0) { if(m>1) mix(m-1, 3-a); //cout<<0<<" "<<a<<endl; printf("0 %d\n", a); for(i = m-1; i > 0; i--) //cout<<3-a<<" "<<a<<endl; printf("%d %d\n", 3-a, a); } } void dump(int m, int a) { int i; if(m>0) { for(i = m; i > 1; i--) //cout<<a<<" "<<1-a<<endl; printf("%d %d\n", a, 1-a); //cout<<a<<" "<<2<<endl; printf("%d 2\n", a); if(m > 1) dump(m-1, 1-a); } } int main() { int n; int a,b; //cin>>n; scanf("%d", &n); mix(n-1,1); //cout<<0<<" "<<2<<endl; printf("0 2\n"); dump(n-1,1); return 0; }
非递归方式代码:
#include <iostream> using namespace std; int main() { int n, i, j, empty; cin>>n; empty = 1 + n % 2; for(i = 0; i < n - 1; i ++) { cout<<"0 "<<empty<<endl; for(j = 0; j < i; j ++) { cout<<3 - empty<<' '<<empty<<endl; } empty = 3 - empty; } cout<<"0 2"<<endl; empty = 0; for(i = 0; i < n - 1; i ++) { for(j = 0; j < n - 1 - i - 1; j ++) { cout<<1 - empty<<' '<<empty<<endl; } cout<<1 - empty<<" 2"<<endl; empty = 1 - empty; } return 0; }
有趣的是:知道了搬运方式之后,我们并不需要维护整个汉诺塔的状态。
相关文章推荐
- 4000 汉诺塔问题及其变种
- 用递归法:设计算法求解汉诺塔问题,并编程实现。 (1) Hanoi(汉诺)塔问题分析 这是一个古典的数学问题,是一个用递归方法解题的典型例子。问题是这样的:古代有一个梵塔,塔内有3个座 A,B,C
- 汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。
- 关于汉诺塔问题的一个疑问
- 一个sql的问题
- 下午解决了一个问题
- GIS图形的地理方向——一个常被忽视的问题
- 由一个性能问题引出的.net概念
- 一个典型的例子解决常见的高级Windows程序设计问题
- 一个debug应用程序出现运行时诊测错误assert的问题
- 碰到了一个GDI+的问题,郁闷ing。。。
- 一个比较郁闷的问题
- 我用c语言写了一个关于商人过河的问题
- 今天看StarterKit.Communities中的关于获取路径部分,发现一个问题?难道老外也会不仔细看MSDN,还是?
- GC的一个问题 [Koffer]
- 偶然发现的一个有点奇怪的SQL语句问题
- 真正的问题——这不是一个笑话
- 小心的使用消息传送--传送给多个View类时需要引起注意的一个问题
- 发现好的网站,解决了一个技术问题
- 一个值得大家来考虑的DLL问题