引例:迷宮問題
首先我們來想象一只老鼠,在一座不見天日的迷宮內,老鼠在入口處進去,要從出口出來。那老鼠會怎么走?當然可以是這樣的:老鼠如果遇到直路,就一直往前走,如果遇到分叉路口,就任意選擇其中的一條繼續往下走,如果遇到死胡同,就退回到最近的一個分叉路口,選擇另一條道路再走下去,如果遇到了出口,老鼠的旅途就算成功結束了。
深度優先搜索的基本原則就是這樣:按照某種條件往前試探搜索,如果前進中遭到失敗(正如老鼠遇到死胡同)則退回頭另選通路繼續搜索,直到找到滿足條件的目標為止。
遞歸程序設計
然而要實現這樣的算法,我們需要用到編程的一大利器---遞歸。當一個函數直接或者間接的調用了自己本身的時候,則發生了遞歸。
講一個更具體的例子:從前有座山,山里有座廟,廟里有個老和尚,老和尚在講故事,講什么呢?講:從前有座山,山里有座廟,廟里有個老和尚,老和尚在講故事,講什么呢?講:從前有座山,山里有座廟,廟里有個老和尚,老和尚在講故事,講什么呢?講:„„„„。好家伙,這樣講到世界末日還講不玩,老和尚講的故事實際上就是前面的故事情節,這樣不斷地調用程序本身,就形成了遞歸。萬一這個故事中的某一個老和尚看這個故事不順眼,就把他要講的故事換成:“你有完沒完啊!”,這樣,整個故事也就嘎然而止了。
我們編程就要注意這一點,在適當的時候,就必須要有一個這樣的和尚挺身而出,把整個故事給停下來,或者說他不再往深一層次搜索,要不,我們的遞歸就會因計算機棧空間大小的限制而溢出,稱為stack overflow。
遞歸的經典實例:
int factorial(int n) { if (n == 0) //基線條件(base case) { return 1; } else { return n * factorial(n - 1); //將問題規模逐漸縮小,或者說轉化為更小 更簡單的子問題 } }
引入DFS 水仙花數:
一個三位數abc如果滿足abc = a^3 + b^3 + c^3 那么就把
這個數叫做水仙花數,寫一個程序,求出所有的水仙花數。
廣義水仙花數:
如果一個N位數所有數碼的N次方的和加起來等於這個數字本身,我們把這樣的數叫做廣義水仙花數,容易看出來水仙花數是N = 3的廣義水仙花數現在,我們的任務是,輸入一個m (m < 7) ,讓你求出所有滿足N = m的廣義水仙花數。 3 (153 370 371 407) 5 (54748 92727 93084)
方法:數據規模很小,可以直接枚舉所有情況,然后判斷是否滿足條件。 難點:循環層數不確定
於是我們現在的問題是,怎么實現這個m重循環?
答案是:遞歸。
m重循環的實現:
void dfs(int deep){ if (deep > m) { //check answer } else if (deep <= m) { for (i = 1; i <= n; i++) dfs(deep + 1); } }
沒錯,這個就是深度優先搜索(Depth-First-Search),那么它為什么叫DFS呢?這就是我下面要講的:
1、搜索樹就是,搜索過程中所形成的樹形結構。(圖)
2、很容易發現,DFS我們在計算那個問題的時候,總是盡量往深里走,也就是說 深度比廣度 優先,所以這種方法叫做深度優先搜索。
3、同樣,如果我們一層一層搜索,這樣,它的廣度先得到了擴展,就叫做廣度優先搜索(Breadth-First-Search, BFS)。
深度優先搜索解決問題的框架
void dfs(int deep, State curState) { if (deep > Max) //深度達到極限 { if (curState == target) //找到目標 { //... } } else{ for (i = 1; i <= totalExpandMethod; i++) { dfs(deep + 1, expandMethod(curState, i)); } } }