1.汉偌塔由来
汉偌塔首先由法国的数学家Edouard Lucas于1883年提出的,它有3个桩,在其中一个桩上会有8个盘子,盘子的大小按照从上到下递减的次序排列。目标是把所有的盘子从一个桩上移动到另外一个桩上,每次只能移动一个盘子,并且永远不能把一个大尺寸的盘子放在一个小尺寸的盘子上。
印度教中也有一个梵天塔的传说类似于汉偌塔,在这个传说中,有64个纯金的盘子放置在宝石柱上。神最初放置这些金制的盘子在第一个柱子上并且命令一组牧师根据前面提到的规则把它们移动到第三个柱子上。牧师们日夜不停的完成自己的工作,当他们完成工作后,塔会崩溃,并且世界会结束。
2.问题的分析
在梵天塔中有64个盘子需要移动,在汉偌塔中,需要移动8个盘子。那么,对该问题的普遍化就是:假如需要移动N个盘子,情况会如何呢?普遍化的优点在于我们可以方便的缩小问题的规模。很显然,移动64或者8个盘子的规律不是那么直观,但是,发现移动1或者2个盘子的规律,就很容易。
为了叙述方便,假定三个柱子分别是a,b和c,盘子起初都被堆放到柱子a,我们的目的是按照上面介绍的规则把盘子都从柱子a移动到柱子c。并且盘子按照1~n来编号,号码越大,盘子的尺寸越大。
*盘子的数目为1的时候,很显然,直接把盘子1从柱子a移动到柱子c即可。
*盘子的数目为2的时候,(a)盘子1从a移动到b; (b)盘子2从a移动到c; (c)盘子1从b移动到c。
*盘子的数目为3的时候,(a)盘子1从a移动到c; (b)盘子2从a移动到b; (c)盘子1从c移动到b; (d)盘子3从a移动到c; (e)盘子1从b移动到a; (f)盘子2从b移动到c; (g)盘子1从a移动到c。
注意观察盘子数目不同时的差异,可以发现当盘子数目为1的时候,并不需要柱子b。当盘子数目大于1的时候,就需要柱子b来中转盘子。这给了我们解决问题的线索:为了移动盘子数目为n时候的汉偌塔,我们可以先把n-1个盘子移动到一个中转的柱子上(在前面的介绍中,就是柱子b),然后移动第n个盘子(最大的一个盘子)到目的柱子上。
具体来说,在前面假设的前提下,解决汉偌塔问题的思路如下 :
1.移动n-1个盘子到柱子b。
2.移动第n个盘子到柱子c。
3.移动柱子b上的n-1个盘子到柱子c。
3.CPP程序实现
CPP代码如下 :
// =====================================================================================
// Filename: hanrt.cpp
//
// Description: 汉偌塔算法CPP实现
//
// Version: 1.0
// Created: 2007-4-6 10:31:00 中国标准时间
// Revision: none
// Compiler: vs2003
//
// Author: sherman (soloer), hugowang@vip.sohu.com
// Company: soho
// =====================================================================================
#include <iostream>
#include <tchar.h>
using namespace std;
/***************************************************************************************
moveplate 移动汉偌塔盘子的动作
input :
-- plate -> 要移动的盘子
-- towerSrc -> 盘子的源
-- towerDst -> 盘子的源
moveplate(plate, towerSrc, towerDst)
* out("move plate %plate from tower %towerSrc to tower %towerDst.")
***************************************************************************************/
void movePlate(int plateNum, char towerSrc, char towerDst) {
cout << "move plate " << plateNum << " from tower '" << towerSrc
<< "' to tower '" << towerDst << "'." << endl;
}
/***************************************************************************************
;; hanrt 移动汉偌塔的盘子
input :
-- plate -> 汉诺塔盘子数量
-- towerA, towerB, towerC -> 汉诺塔A,B,C
hanrt(plate, towerA, towerB, towerC)
* if plate != 1, then
-- hairo(plate-1, towerA, towerC, towerB)
moveplate(plate, towerA, towerC)
-- hairo(plate-1, towerB, towerA, towerC)
* else
moveplate(plate, towerA, towerC)
***************************************************************************************/
void hanrt(unsigned int plateCount, char towerA, char towerB, char towerC) {
if(1 != plateCount) {
hanrt((plateCount - 1), towerA, towerC, towerB);
movePlate(plateCount, towerA, towerC);
hanrt((plateCount - 1), towerB, towerA, towerC);
}
else {
movePlate(plateCount, towerA, towerC);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
hanrt(4, 'a', 'b', 'c');
return 0;
}
输出 :
move plate 1 from tower 'a' to tower 'b'.
move plate 2 from tower 'a' to tower 'c'.
move plate 1 from tower 'b' to tower 'c'.
move plate 3 from tower 'a' to tower 'b'.
move plate 1 from tower 'c' to tower 'a'.
move plate 2 from tower 'c' to tower 'b'.
move plate 1 from tower 'a' to tower 'b'.
move plate 4 from tower 'a' to tower 'c'.
move plate 1 from tower 'b' to tower 'c'.
move plate 2 from tower 'b' to tower 'a'.
move plate 1 from tower 'c' to tower 'a'.
move plate 3 from tower 'b' to tower 'c'.
move plate 1 from tower 'a' to tower 'b'.
move plate 2 from tower 'a' to tower 'c'.
move plate 1 from tower 'b' to tower 'c'.
4.总结
最近在看《银英传》的时候,小莱一句台词,让自己印象深刻,就是“知道要做什么和实际做了什么,它们之间的距离有一光年”。学习算法也是如此,汉偌塔算法简单,是很多算法书上讲解“递归算法”的起手技。但是就这个简单的算法,最初由于并没有细看和思考问题的推导过程,而看不明白算法书上的C代码。然后“看明白了”算法,但是由于没有实际编写代码,而以为懂了,然而却无法在需要的时候,重写代码。接下来,实际编写了代码,但是没有用自己的语言来介绍汉偌塔问题的推导过程,其实依然还存在盲区。只有在编写了代码,并且也写了这篇笔记后,才貌似明白了汉偌塔问题的算法。
所以,以后要多多写blog,费的时间不多,收获不见的小。 :)
技术细节方面的感想,如下 :
*python,lisp或许比CPP适合描述算法,但是它们的受众比C/C++要少。所以描述算法的时候,最好的语言或许是自然语言。
*自然语言的缺点在于它不能在电脑上运行,而可以运行自然语言的人脑,又不够可靠和稳定。 :)
*所以,或许不错的选择是先用自然语言描述算法,解释算法的思想。然后再用一种程序设计语言来在电脑上运行算法,以验证算法的正确性。
5.参考资料
《concrete mathematics》
google到的一些关于汉偌塔的资料