從一些例子想想解決遞迴問題的思路
之前遇上的遞迴問題都比較簡單,至少都有一個明確的切入點,例如隨機生成迷宮、掃雷等,通常都是從一點開始向外擴充套件,那一點就是切入點。
直到碰上漢諾塔問題,兩眼一抹黑,沒有思路。還是靠看別人的思路才解決的。
打算以後碰上新的遞迴問題,都把思路寫一寫。
1、漢諾塔
漢諾塔問題概述就不寫了
假設共有n個圓盤在A柱(圓盤當前所在柱)上,B(中間柱)、C(目標柱)柱上暫時空空如也,想要把n個圓盤全部移到C柱上。
由於漢諾塔問題的限制導致勢必有那麼一個時刻,前n-1個圓盤從A柱轉移到了B柱上,此時n號圓盤才能離開A柱到達C柱,對於留在B柱上的n-1個圓盤最終也要轉移到C柱。
於是問題分為三步:1、前n-1個圓盤從A柱轉移到B柱
2、n號圓盤從A柱轉移到C柱
3、前n-1個圓盤從B柱轉移到C柱
2好說,主要問題是1、3。
1、前n-1個圓盤從A柱轉移到B柱
由於第n個圓盤比其他所有盤子都大,對於圓盤的轉移沒有任何影響,所以這個問題與漢諾塔本身相比除了圓盤少了一個(n到n-1),終點柱子變了(C柱變B柱)以外,沒有任何區別,甚至可以看做是另一個漢諾塔問題。
那麼按照之前的思路有:1.1、前n-2個圓盤從A柱轉移到C柱
2.2、n-1號圓盤從A柱轉移到B柱
2.3、前n-1個圓盤從C柱轉移到B柱
3、問題也是同理有:3.1、前n-2個圓盤從B柱轉移到A柱
3.2、n-1號圓盤從B柱轉移到C柱
3.3、前n-1個圓盤從A柱轉移到C柱
既然問題分支的解決方法與問題本身幾乎一致,那就可以考慮下遞迴能否解決了。
如果現在搭一個遞迴的架子,那大概是這樣:
void hanion(int n ,xxxxxxxx)//n個圓盤的轉移 { hanion(--n,xxxxxxx);//n-1個圓盤的轉移 printf("第%d個圓盤從xxxx柱---->xxxx柱",n,xxxxx,xxxxx);//n號圓盤的轉移 hanion(--n,xxxxxxx);//n-1個圓盤的轉移 }
一個大概的樣子有了,那怎麼讓這個函式知道這些圓盤當前在哪根柱子上,接下來又要去那根柱子上呢?
我們觀察1、2、3 以及 1對應的1.1、1.2、1.3 還有3對應的3.1、3.2、3.3 可以發現
任何一個x個圓盤的轉移問題,如果按上述思路分為三步,那麼其:
圓盤當前所在柱(A) 會成為 第1、2步的 圓盤當前所在柱
中間柱(B) 會成為第1步的 目標柱 和第3步的 圓盤當前所在柱
目標柱(C) 會成為 第1、2步的 目標柱
不管函式巢狀到何種程度,這個關係都不會改變,只要將這個關係寫入函式宣告,在後續的遞迴執行中就能正確表示圓盤的位置與轉移關係了。
可以定義三個形參a,b,c,用它們代表圓盤當前所在柱、中間柱、目標柱。
void hanion(int n ,char a,char b,char c)//n個圓盤的轉移
那麼按照之前得到的規律,函式定義會變成:
void hanion(int n ,char a,char b,char c)//n個圓盤的轉移 { hanion(--n,a,c,b);//n-1個圓盤的轉移 printf("第%d個圓盤從%c柱---->%c柱",n,a,c);//n號圓盤的轉移 hanion(--n,b,a,c);//n-1個圓盤的轉移 }
現在函式還缺一個可靠的出口,顯然光靠一個return 並不行。
針對n,容易想到這玩意兒小於1就沒有意義了,所以要加上:
if(n==1) return;
它需要加在當前所寫的函式定義的開頭位置,否則無法阻止遞迴的進行,但要注意既然寫在printf語句前,那麼這段程式就不會執行了,而它想輸出的是 “第1個圓盤從xxx柱--->xxx柱”,這是有用的。
所以要在if語句中加上 printf("第1個圓盤從%c柱--->%c柱",a,c); 或printf("第%d個圓盤從%c柱--->%c柱",n,a,c);(此時n就是1) ,改為:
void hanion(int n ,char a,char b,char c)//n個圓盤的轉移 { if(n==1) { printf("第1個圓盤從%c柱--->%c柱",a,c); return 0; } hanion(--n,a,c,b);//n-1個圓盤的轉移 printf("第%d個圓盤從%c柱--->%c柱",n,a,c);//n號圓盤的轉移 hanion(--n,b,a,c);//n-1個圓盤的轉移 }