通俗易懂的解釋~
遊戲規則:
有A,B,C三根針,將A針上N個從小到大疊放的盤子移動到C針,一次只能移動一個,不重複移動,小盤子必須在大盤子上面。
問題:
總的移動次數是多少?
分析:
首先明確,我們的目標是將A針上所有N個盤子移動至C針。而對於B針,我們可以將之看成一箇中轉站。
這個問題,順向思維或者逆向思維道理是相同的,都太麻煩。我們不妨從中間開始思考
||: 規則要求小盤子必須在大盤子之上。試想這個過程中,必然會經歷那麼一個步驟,即有一大坨N-1個盤子在B針這個中轉站,而我們正將最大那個盤子(即第N個盤子)從A針移動至C針。
只有經歷“移動最大盤子”這個步驟,餘下的事情纔有可能實現。而在此之前,我們所要做的事情,就是讓“移動最大盤子”這個步驟得以實現。
現在,遊戲整個過程以“移動最大盤子”爲中央,被分爲了兩部分。即(前)“將那坨N-1個盤子從A針移動到B針”,(中)“移動最大盤子”,(後)“將坨N-1個盤子從B針移動到C針”。
這是我們意識到,(前)與(後)操作道理是相似的。不去管那個最大盤子,(前)是以C針爲中轉站,(後)是以A針爲中轉站。因此兩者所需的移動次數應當是相等的。這意味着我們只要計算出其中一者的移動次數,然而乘以2,在加上“移動最大盤子”的那1次,就是這場遊戲的總移動次數了。
用數學語言表達,假設(前)“將N-1個盤子從A針移動到B針”所需次數爲Hn-1,總移動次數爲Hn,那麼可以得出的關係就是:Hn=(Hn-1)x 2 + 1.
其實當我們得出這個算式的時候,稍微聰明一點的人已經明白,這就是一個遞推公式,可以直接用此公式得出Hn的通解。
但是LZ比較笨,就是不明白,爲什麼這個公式就可以套用呢?
那麼就乾脆繼續思考吧。
讓我們再想象一個情景:最大那個盤子在剛剛從A針被移動到C針,而那坨N-1個盤子還在B針蠢蠢欲動地等待着,即處於(中)->(後)的這個狀態。
怎麼移動這N-1個盤子呢?
其實這時候,問題已經回到了筆者標示“||:”符號的地方。“||:”是樂譜中的反覆記號,而我們要做的,就是重複上面的步驟,但是要將N替換爲N-1,因爲現在只剩下N-1個盤子需要移動。而中轉站則從B變成了A(鑑於這時盤子都在B針)。目標仍然是C針。下一次重複的時候,只剩下N-2個盤子需要移動,中轉站又回到B,目標不變仍然是C針。……整個過程中,變化的只是中轉站(在A與B之間輪換),以及剩下那些所需要移動的盤子的總數(越來越少)而已。
那麼那個大盤子怎麼辦?不去管它嗎??
正解!!
因爲你已經把它移到C針,已經完成了這個移動步驟,它不會影響之後的操作。提醒自己牢記遊戲規則,大盤子永遠在小盤子下面,而你也不需要再重複移動它——“不重複移動”,正是遊戲規則的要求!
於是
Hn=Hn-1 x 2 + 1 這個公式,就可以套用、套用、套用……直到H3=7,H2=3,H1=1。
最後,用最懶的數學歸納法證明通項公式
Hn = 2^n - 1 吧!沒辦法,LZ就是比較懶嘛~
代碼實例
//漢諾塔
#include <stdio.h>
void hannuota(int n, char A, char B, char C)
{
/*
如果是一個盤子,直接將盤子將a移到c
否則先將a柱子上面的n-1個盤子藉助c移到b,
然後直接將a柱子上的盤子從A移到c,
最後將b柱子上的n-1個盤子藉助a移到c
*/
if(1 == n)
{
printf("將編號爲%d的盤子直接從%c柱子移到%c柱子\n",n, A, C);
}
else
{
hannuota(n-1, A , C , B);
printf("將編號爲%d的盤子直接從%c柱子移到%c柱子\n",n, A, C);
hannuota(n-1, B, A, C);
}
}
int main(void)
{
char ch1 = 'A';
char ch2 = 'B';
char ch3 = 'B';
int n;
printf("請輸入要移動盤子的個數:");
scanf("%d", &n);
hannuota(n, 'A', 'B', 'C');
return 0;
}
參考自:
YIHE陳