演算法之路(四)----漢諾塔(又稱河內之塔)
漢諾塔是很簡單也很經典的演算法之一。
漢諾塔是根據一個傳說形成的數學問題:
有三根杆子A,B,C 。A杆上有N個(N>1)穿孔圓盤,盤的尺寸由下到上依次變小。要求按下列規則將所有圓盤移至C杆:
* 1 每次只能移動一個圓盤;
* 2 大盤不能疊在小盤上面。
提示:可將圓盤臨時置於B杆,也可以將A杆移除的圓盤重新移動回A杆,但都必須遵循上述兩條規則。
問:如何移?最少要移動多少次?
傳說
最早發明這個問題的人是法國數學家愛德華*盧卡斯。
傳說印度某間寺院有三根柱子,上串64個金盤。寺院裡的僧侶依照一個古老的預言,以上述規則移動這些盤子;預言說當這些盤子移動完畢,世界就會滅亡。這個傳說叫做
若傳說屬實,僧侶們需要264− 1步才能完成這個任務;若他們每秒可完成一個盤子的移動,就需要5849億年才能完成。整個宇宙現在也不過137億年。
這個傳說有若干變體:寺院換成修道院、僧侶換成修士等等。寺院的地點眾說紛紜,其中一說是位於越南的河內,所以被命名為“河內塔”。另外亦有“金盤是創世時所造”、“僧侶們每天移動一盤”之類的背景設定。
佛教中確實有“浮屠”(塔)這種建築;有些浮屠亦遵守上述規則而建。“河內塔”一名可能是由中南半島在殖民時期傳入歐洲的。
解答
如取N=64,最少需移動264
在真實玩具中,一般N=8;最少需移動255次。如果N=10,最少需移動1023次。如果N=15,最少需移動32767次;這就是說,如果一個人從3歲到99歲,每天移動一塊圓盤,他最多僅能移動15塊。如果N=20,最少需移動1048575次,即超過了一百萬次。
解法
解法的基本思想是遞迴。假設有A、B、C 三個塔,A塔有N塊盤,目標是把這些盤全部移動到C塔。那麼先把塔頂部的N-1塊盤移動到B塔,再把A塔剩下的大盤移動到C,最後把B塔的N-1塊盤移動到C。
每次移動多於一塊盤時,則再次使用上述演算法來移動。
怎麼來理解呢?
我們可以倒著理解,要將A塔上的所有圓盤移動到C塔,且所有圓盤是下大上小。那麼必定有一個過程是最大的圓盤(也就是第N個圓盤)從A移動到C。那麼必須保證上面的N-1個圓盤全在B塔,且圓盤滿足上面小,下面大。當第N個圓盤從A移動到C之後,又得把N-1個圓盤從B塔移動到C塔,這樣工作就完成了。
但是怎麼把A塔上的N-1個圓盤移動到B塔呢?
這裡需要一點想象力,可以想象成只有N-1個圓盤,從A塔移動到B塔(此時的B塔其實就相當於上面的C塔),我們稱A塔為A1塔,B塔為C1塔,C塔為B1塔,那麼問題就變成了如何將N-1個盤從A1塔移動到C1塔?
同樣的需要將上面的N-2個圓盤從A1塔移動到B1塔,然後將第N-1個圓盤從A1塔移動到C1塔,然後再將B1塔上的N-2個圓盤移動到C1塔。
同理,遞推第N-2個塔…..。
將 B塔上的 N-1個盤,移動到C塔的過程與上面原理一樣。
遞迴解
以Objective-C語言實現:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
int n = 9;
[self hannoi:n from:@"A" buffer:@"B" to:@"C"];
NSLog(@"一共 %d 步",count);
}
- (void)hannoi:(int)n from:(NSString *)from buffer:(NSString *)buffer to:(NSString *)to
{
if (n == 1) {
NSLog(@"Move disk %d from %@ to %@", n, from, to);
} else {
[self hannoi:n-1 from:from buffer:to to:buffer];
NSLog(@"Move disk %d from %@ to %@", n, from, to);
[self hannoi:n-1 from:buffer buffer:from to:to];
}
}
console : 一共 511 步
以C++語言實現:
using namespace std;
#include <iostream>
#include <cstdio>
void hannoi (int n, char from, char buffer, char to)
{
if (n == 1) {
cout << "Move disk " << n << " from " << from << " to " << to << endl;
} else {
hannoi (n-1, from, to, buffer);
cout << "Move disk " << n << " from " << from << " to " << to << endl;
hannoi (n-1, buffer, from, to);
}
}
int main()
{
int n;
cin >> n;
hannoi (n, 'A', 'B', 'C');
return 0;
}
通過以上遞迴過程可知hannoi(n) = 2 * hannoi(n-1) + 1 ,hannoi(n) = 2n-1 + 2n-2 + 2n-3+ …… + 22 + 2 +1。
對等比數列求和得出hannoi(n) = 2n -1。