漢諾塔問題非遞迴演算法集錦

2021-06-27 08:27:15 字數 3991 閱讀 9663

漢諾塔問題非遞迴演算法集錦

漢諾塔問題介紹:

在印度,有這麼乙個古老的傳說:在世界中心貝拿勒斯(在印度北部)的聖廟裡,一塊黃銅板上插著三根寶石針。印度教的主神梵天在創造世界的時候,在其中一根針上從下到上地穿好了由大到小的64片金片,這就是所謂的漢諾塔。不論白天黑夜,總有乙個僧侶在按照下面的法則移動這些金片,一次只移動一片,不管在哪根針上,小片必在大片上面。當所有的金片都從梵天穿好的那根針上移到另外一概針上時,世界就將在一聲霹靂中消滅,梵塔、廟宇和眾生都將同歸於盡。

漢諾塔問題遞迴演算法思路簡單且**簡潔,算得上最美**之一:

int count = 0;//全域性變數,累積操作步數

void hanoi(int n, char a, char b, char c)//遞迴演算法

}

但是,非遞迴演算法就沒那麼容易理解了,筆者蒐集整理了以下幾種方案(還有幾種方案,由於本人一時未能理解,暫時不予貼出,同時請聰明的你補充簡明易懂的演算法,謝謝!)

常見的思路是利用棧對遞迴演算法進行非遞迴轉換,李春葆老師編著的《資料結構(c語言篇)——習題與解析》(清華大學出版社)一書中介紹了一種方法。該方法設計了乙個資料結構    struct act s[2000];  儲存當前操作資訊,其中flag==0表示直接移動num號圓盤,否則需要進一步分解;num表示當前操作移動盤子的編號,x,y,z表示當前操作對應的3個塔柱,分別是出發柱,中點柱和目標柱。

演算法的基本思路與中序遍歷二叉樹很類似,深入理解該演算法後,我對書中的**進行了一些改進,使其更簡潔明瞭。**如下:

void hanoi_1(int n, char a, char b, char c)//非遞迴演算法1

s[2000]; //儲存當前操作資訊,flag==0表示直接移動num號圓盤,否則需要進一步分解

int top, m;

char ta, tb, tc;

s[0].flag = 1;//初值入棧

s[0].num = n;

s[0].x = a;

s[0].y = b;

s[0].z = c;

top = 0;

count = 0;

while (top >= 0)

else

}}

非遞迴演算法1是對遞迴演算法的模擬過程,但由於其把當前操作分成兩種型別,即直接移動和進一步分解,人為地增加了演算法的複雜度。實際上,仔細分析二叉樹的中序遍歷非遞迴演算法(詳見拙作《

史上最簡明易懂非遞迴遍歷二叉樹演算法

》我們可以發現,完全可以採用類似的方法把漢諾塔非遞迴演算法轉化為非遞迴演算法,思維方式幾乎一模一樣。**如下:

void hanoi_2(int n, char a, char b, char c)//非遞迴演算法2

s[max]; //儲存當前操作資訊

int top = -1;

int count = 0;

while (n > 0 || top >= 0)

else//輸出並退棧,搜尋右孩子,即將 hanoi(n-1, b, a, c); 操作入棧

}}

非遞迴演算法1和2是利用棧模擬遞迴過程的基本方法,我們還可以從另外乙個角度來分析漢諾塔問題。對於有n個盤子的漢諾塔問題,需要操作的步驟為2^n – 1,如果每乙個步驟看成乙個節點,則剛好構成一棵滿二叉樹,樹高h與盤子數量的關係為h==n。結點所在的層數與對應盤子的編號關係為level==n+1-level,即盤子1在第n層,盤子n在第1層;若某個結點的操作為「盤子n從a->c」,則其左孩子操作為「盤子n-1從a->b」,右孩子操作為「盤子n-1從b->c」;中序遍歷滿二叉樹,結點的編號恰好對應移動的次序。

因此我們可以構造一棵滿二叉樹,然後中序遍歷該二叉樹即可。**如下:

void hanoi_3(int n, char a, char b, char c)//非遞迴演算法3,缺點是需要輔助空間太大 

bt[132000]; //儲存每一步操作的滿二叉樹,假設n<=17.

int s[max] = ;

int i, top, count = 0;

bt[1].num = n;//為根結點賦值

bt[1].x = a;

bt[1].y = b;

bt[1].z = c;

n = pow(2, n-1);

for (i=1; i= 0)

else//輸出並退棧,搜尋右孩子

} }

演算法3的思路簡明易懂,**也很簡潔,但是有乙個致命缺陷,就是需要的輔助空間太多,當n較大時不太適用。有沒有更好的方法呢?

①樹高h與盤子數量的關係為h==n。結點所在的層數與對應盤子的編號關係為level==n+1-level,即盤子1在第n層,盤子n在第1層;

②中序遍歷滿二叉樹,結點的編號恰好對應移動的次序。

③第n層共2^(n-1)個結點,它們能被2^0整除且不能被2^1整除: 1,3,5,7,9,...2^n - 1.

第n-1層共2^(n-2)個結點,它們能被2^1整除且不能被2^2整除: 2,6,10,14,...2^n - 2.

第3層共2^2個結點,它們不能被2^(n-2)整除: 2^(n-3) * 1, 2^(n-3) * 2, 2^(n-3) * 3, 2^(n-3) * 4

第2層共2^1個結點,它們不能被2^(n-1)整除: 2^(n-2) * 1, 2^(n-2) * 2

第1層共2^0個結點,它不能被2^n整除: 2^(n-1) *1

④奇數層各個結點的操作依次是:a->c、c->b、b->a、a->c、c->b、b->a、…模3迴圈;

偶數層各個結點的操作依次是:a->b、b->c、c->a、a->b、b->c、c->a、…模3迴圈;

綜合以上分析,得出以下結論

①盤子數n確定後,步驟總數m=2^n-1;

②第i(0③第i(0根據上述分析,我們可以給出相應的**:

void hanoi_4(int n, char a, char b, char c)//非遞迴演算法4,根據滿二叉樹的規律輸出 

} else //偶數層

} }}

演算法3和4屬於利用滿二叉樹特徵而得出的方法,談祥柏老師在《數學營養菜》中提到過一位美國學者發現的方法,只要輪流進行兩步操作就可以了。

首先把三根柱子按順序排成品字型,把所有的圓盤按從大到小的順序放在柱子a上。根據圓盤的數量確定柱子的排放順序:若n為偶數,按順時針方向依次擺放a b c;若n為奇數,按順時針方向依次擺放 a c b。

(1)按順時針方向把圓盤1從現在的柱子移動到下一根柱子,即當n為偶數時,若圓盤1在柱子a,則把它移動到b;若圓盤1在柱子b,則把它移動到c;若圓盤1在柱子c,則把它移動到a。

(2)接著,把另外兩根柱子上可以移動的圓盤移動到新的柱子上。即把非空柱子上的圓盤移動到空柱子上,當兩根柱子都非空時,移動較小的圓盤。這一步沒有明確規定移動哪個圓盤,你可能以為會有多種可能性,其實不然,可實施的行動是唯一的。

(3)反覆進行(1)(2)操作,最後就能按規定完成漢諾塔的移動。

有了這個步驟說明,**是很容易寫出來的。**如下:

void hanoi_5(int n, char a, char b, char c)//非遞迴演算法5 

p[3]; //儲存各個柱子上的盤子資訊

int i, count, next, pre;

for (i=0; i%c ", ++count, p[i%3].pos, p[(i+1)%3].pos);//將1號盤從順時針移動到下乙個柱子

if (count == n)

break;

++i;

next = (i+ 1) % 3;

pre = (i - 1) % 3;

if (p[next].top < 0 || p[pre].top >= 0 && p[pre].s[p[pre].top] < p[next].s[p[next].top])

else

}}

漢諾塔非遞迴演算法

輸入格式 輸入為乙個正整數n,即起始柱上的盤數。輸出格式 每個操作 移動 佔一行,按柱1 柱2的格式輸出 輸入樣例 3輸出樣例 a c a b c b a c b a b c 乙個美國學者總結得到 所有的漢諾塔移動可以總結為重複的兩步,我們假設現在最小的圓盤在a柱子上,柱子為a,b,c 第二步 對a...

漢諾塔問題遞迴與非遞迴演算法

漢諾塔問題描述如下 有 a b c 3 根針,n 個圓盤 從 1.n 從上到下,按小到大順序放在 a 處,要求每次移動乙個,並保持從小到大的疊放順序,利用 c,把 n 個盤子移動到 b 處。遞迴演算法比較容易理解 fn hanoi n hanoi move n,a b c fn hanoi move...

非遞迴 遞迴 漢諾塔演算法實踐

漢諾塔演算法是很多公司的面試題,經常會讓手寫,這裡總結了一下 1 最最最常見的也是最簡單的漢諾塔演算法,遞迴 這也是學習遞迴的乙個經典演算法題 漢諾塔演算法 遞迴 ps 列印移動過程 param level 層數 param from 起始位置 param to 目標位置 param other 多...