搜尋演算法 Search Algorithm


搜尋演算法在很多地方都能應用, 在這個主題會提到的搜尋演算法有五種:
  1. Depth-first search (DFS) 深度優先搜尋法
  2. Depth-limited search (DLS) 深度限制搜尋法
  3. Iterative deepening search (IDS) 加深式搜尋法
  4. Breadth-first search (BFS) 廣度優先搜尋法
  5. A* search A*搜尋法
我們在這個主題會以樹(Tree)來做為搜尋的目標,而不是圖(Graph),這是要先說明的。畢竟用圖來說明是比較累的事情。

首先來看一個Tree,這個Tree將用來做整個主題的範例。


幾個名詞先定義一下,等一下用到的時候才不會混淆。
那麼,我們從DFS開始吧。

Depth-first search 深度優先搜尋法 深度優先搜尋法的基本精神,就是不管橫的,只管直的,一股腦的往下找,找不到再回頭旁邊的路,所以叫深 度優先。舉個例子就懂了:
我們現在的位置是A,看一下A是不是我們要找的,如果不是,就把A展開,然後看到有三個點B、C與D。
這個時候,我們先把C與D放著不管(不檢查是不是我們要的),看看B是不是我們要的,若B也不是, 就再把B給展開,然後發現E與F。
這時F還是一樣放著不看,然後檢查E,若E也不是,就把E展開,看到J。
檢查J是不是我們要的答案。如果J不是呢?這時就要回頭啦,從J回頭後,看到E。
E除了J這個孩子外,也沒別的了,所以只好再返回一次,回到B。
B的另一個兒子F我們還沒檢查過,這時檢查F是不是答案。
若F還不是我們要的,就再往下走,看到K。
檢查K,依然不是?那就展開K,看到O。
檢查O,如果這時O也不是答案呢?再返回,發現K沒別的孩子了,再返回,F也沒有別的孩子,只好再返回 到B。
可是B也沒別的兒子了,只好再返回到A。
A的孩子剛才看到了有B、C與D,這時B的孩子們都走完了,就從C開始走,重覆剛才的觀念,一直到走到N為止。
如果一直都找不到我們要的解答,就宣告失敗。

所以整棵樹的走法就是A->B->E->J->F->K->O->C->G->L->M->D->H->I->N

DFS的好處在那裡?好處是在走的時候,我們只要「記住目前路徑的點」即可,假設我們現在從A走到J,我們 只需記往ABCDEF即可(J是要處理的點,不用記),較正式的說法是,若這棵樹的分支數是b(以二元樹來說,b=2,但是以上圖來看,我 們假設b=3),目的地的深度是d,則我們需要記住的點的數目是bd,J在第3層,所以是3×3=9,意思是 最多不會超過9個點。然而,時間上倒是和其它的搜尋法沒什麼分別,最慘最慘就是整棵樹找完嘛,也就是最多 要找bd個點。

DFS被視為非Complete,也就是即使答案存在,它也不一定找的到答案,聽起來很玄嗎?找不到答案是因為 無窮路徑的問題。樹是圖(Graph)的一種,我們必須回到比較廣的定義去看。想像一下,如果J的下面畫個箭頭 指到B去,那麼會變成什麼樣子?A->B->E->J->B->E->J->B...永遠不會停,也永遠不會去走其它的路了!所以 說DFS是非Complete的。

還有一個是Optimal的問題,就是DFS能不能保證能找到最佳解?答案是不能的,因為它笨笨的一直往下挖, 就算答案在它隔壁,它也要挖完這一條路以後返回才知道。

Worst case分析:
Time complexity: O(bd) d: depth, b: number of branches
Space complexity: O(bd)
Complete: No
Optimal: No


Mick's pseudocode


每個新的node都是由特定的operator所「產生(generate)」的,因為在面對未知的問題時,我們不知道現在的 點的孩子長什麼樣子,上面的圖是假設我們都知道點長什麼樣子了,但是我們在設計程式時,是不會知道的。 所以,我們要這樣想,B、C與D是由A「generate」出來的,怎麼去generate呢?就是執行某些特定的operator, 舉個例子好了,把operator想像成「往上走」、「往下走」、「往左走」、「往右走」,這四個operator會 generate四個新的節點,這樣子懂了嗎?所以前面說的「展開」也是這個意思,本來一個點,變成四個點,當然 叫做展開囉。

Push(Queue, node) { Queue-front <- Queue-front+1 Queue[Queue-front] <- node } Pop() { N=Queue[Queue-front] Queue[Queue-front] <- NIL Queue-front <- Queue-front-1 return N } Expand(node) { for each operator generate new node Push(Queue, new node) } DFS(node) { if node is goal then { display path or do anything else return } else { Expand(node) if Queue is not empty then DFS(pop(Queue)) else return False } }

有任何意見請寫信到Mick J.Y.Lin
回上一頁