BFS和DFS优先搜索算法
4、教你通透徹底理解:BFS和DFS優先搜索算法
?
?作者:July??二零一一年一月一日
---------------------------------
本人參考:算法導論?
本人聲明:個人原創,轉載請注明出處。
ok,開始。
翻遍網上,關于此類BFS和DFS算法的文章,很多。但,都說不出個所以然來。
讀完此文,我想,
你對圖的廣度優先搜索和深度優先搜索定會有個通通透透,徹徹底底的認識。
---------------------
咱們由BFS開始:
首先,看下算法導論一書關于 此BFS 廣度優先搜索算法的概述。
算法導論第二版,中譯本,第324頁。
廣度優先搜索(BFS)
在Prime最小生成樹算法,和Dijkstra單源最短路徑算法中,都采用了與BFS 算法類似的思想。
BFS(G, s) 1 for each vertex u ∈ V [G] - {s}2 do color[u] ← WHITE3 d[u] ← ∞4 π[u] ← NIL//除了源頂點s之外,第1-4行置每個頂點為白色,置每個頂點u的d[u]為無窮大,//置每個頂點的父母為NIL。 5 color[s] ← GRAY//第5行,將源頂點s置為灰色,這是因為在過程開始時,源頂點已被發現。 6 d[s] ← 0 //將d[s]初始化為0。 7 π[s] ← NIL //將源頂點的父頂點置為NIL。 8 Q ← ?9 ENQUEUE(Q, s) //入隊//第8、9行,初始化隊列Q,使其僅含源頂點s。 10 while Q ≠ ? 11 do u ← DEQUEUE(Q) //出隊//第11行,確定隊列頭部Q頭部的灰色頂點u,并將其從Q中去掉。 12 for each v ∈ Adj[u] //for循環考察u的鄰接表中的每個頂點v 13 do if color[v] = WHITE 14 then color[v] ← GRAY //置為灰色 15 d[v] ← d[u] + 1 //距離被置為d[u]+1 16 π[v] ← u //u記為該頂點的父母 17 ENQUEUE(Q, v) //插入隊列中 18 color[u] ← BLACK //u 置為黑色
由下圖及鏈接的演示過程,清晰在目,也就不用多說了:?
廣度優先遍歷演示地址: http://sjjg.js.zwu.edu.cn/SFXX/sf1/gdyxbl.html -----------------------------------------------------------------------------------------------------------------ok,不再贅述。接下來,具體講解深度優先搜索算法。
深度優先探索算法 DFS?
//u 為 v 的先輩或父母。
DFS(G) 1 for each vertex u ∈ V [G] 2 do color[u] ← WHITE 3 π[u] ← NIL //第1-3行,把所有頂點置為白色,所有π 域被初始化為NIL。 4 time ← 0 //復位時間計數器 5 for each vertex u ∈ V [G] 6 do if color[u] = WHITE 7 then DFS-VISIT(u) //調用DFS-VISIT訪問u,u成為深度優先森林中一棵新的樹//第5-7行,依次檢索V中的頂點,發現白色頂點時,調用DFS-VISIT訪問該頂點。//每個頂點u 都對應于一個發現時刻d[u]和一個完成時刻f[u]。 DFS-VISIT(u) 1 color[u] ← GRAY //u 開始時被發現,置為白色 2 time ← time +1 //time 遞增 3 d[u] <-time //記錄u被發現的時間 4 for each v ∈ Adj[u] //檢查并訪問 u 的每一個鄰接點 v 5 do if color[v] = WHITE //如果v 為白色,則遞歸訪問v。 6 then π[v] ← u //置u為 v的先輩 7 DFS-VISIT(v) //遞歸深度,訪問鄰結點v 8 color[u] <-BLACK //u 置為黑色,表示u及其鄰接點都已訪問完成 9 f [u] ? time ← time +1 //訪問完成時間記錄在f[u]中。 //完 第1-3行,5-7行循環占用時間為O(V),此不包括調用DFS-VISIT的時間。
??? 對于每個頂點v(-V,過程DFS-VISIT僅被調用依次,因為只有對白色頂點才會調用此過程。
第4-7行,執行時間為O(E)。
因此,總的執行時間為O(V+E)。
?
下面的鏈接,給出了深度優先搜索的演示系統:
圖的深度優先遍歷演示系統:
http://sjjg.js.zwu.edu.cn/SFXX/sf1/sdyxbl.html
?
===============
最后,咱們再來看深度優先搜索的遞歸實現與非遞歸實現
1、DFS 遞歸實現:
2、DFS 非遞歸實現
非遞歸版本---借助結點類型為隊列的棧實現
?? 聯系樹的前序遍歷的非遞歸實現:
?? 可知,其中無非是分成“探左”和“訪右”兩大塊訪右需借助棧中彈出的結點進行.
?? 在圖的深度優先搜索中,同樣可分成“深度探索”和“回訪上層未訪結點”兩塊:
??? 1、圖的深度探索這樣一個過程和樹的“探左”完全一致,
只要對已訪問過的結點作一個判定即可。
??? 2、而圖的回訪上層未訪結點和樹的前序遍歷中的“訪右”也是一致的.
但是,對于樹而言,是提供rightSibling這樣的操作的,因而訪右相當好實現。
在這里,若要實現相應的功能,考慮將每一個當前結點的下層結點中,如果有m個未訪問結點,
則最左的一個需要訪問,而將剩余的m-1個結點按從左到右的順序推入一個隊列中。
并將這個隊列壓入一個堆棧中。
?? 這樣,當當前的結點的鄰接點均已訪問或無鄰接點需要回訪時,
則從棧頂的隊列結點中彈出隊列元素,將隊列中的結點元素依次出隊,
若已訪問,則繼續出隊(當當前隊列結點已空時,則繼續出棧,彈出下一個棧頂的隊列),
直至遇到有未訪問結點(訪問并置當前點為該點)或直到棧為空(則當前的深度優先搜索樹停止搜索)。
?
將算法通過精簡過的C源程序的方式描述如下:
//dfsUR:功能從一個樹的某個結點inV發,以深度優先的原則訪問所有與它相鄰的結點 void dfsUR(PGraphMatrix inGraph,PVexType inV) {PSingleRearSeqQueue tmpQ; //定義臨時隊列,用以接受棧頂隊列及壓棧時使用 PSeqStack testStack; //存放當前層中的m-1個未訪問結點構成隊列的堆棧.//一些變量聲明,初始化動作//訪問當前結點 inV->marked=1; //當marked值為1時將不必再訪問。 visit(inV);do{flag2=0;//flag2是一個重要的標志變量,用以、說明當前結點的所有未訪問結點的個數,兩個以上的用2代表//flag2:0:current node has no adjacent which has not been visited.//1:current node has only one adjacent node which has not been visited.//2:current node has more than one adjacent node which have not been visited. v1=firstAdjacent(inGraph,inV); //鄰接點v1 while(v1!=NULL) //訪問當前結點的所有鄰接點 {if(v1->marked==0) //.. { if(flag2==0) //當前結點的鄰接點有0個未訪問 {//首先,訪問最左結點 visit(v1);v1->marked=1; //訪問完成 flag2=1; ////記錄最左兒子 lChildV=v1; //save the current node's first unvisited(has been visited at this time)adjacent node } else if(flag2==1) //當前結點的鄰接點有1個未訪問 {//新建一個隊列,申請空間,并加入第一個結點 flag2=2;}else if(flag2==2)//當前結點的鄰接點有2個未被訪問 {enQueue(tmpQ,v1);}}v1=nextAdjacent(inGraph,inV,v1);}if(flag2==2)//push adjacent nodes which are not visited. { //將存有當前結點的m-1個未訪問鄰接點的隊列壓棧 seqPush(testStack,tmpQ);inV=lChildV;}else if(flag2==1)//only has one adjacent which has been visited. { //只有一個最左兒子,則置當前點為最左兒子 inV=lChildV;}else if(flag2==0)//has no adjacent nodes or all adjacent nodes has been visited { //當當前的結點的鄰接點均已訪問或無鄰接點需要回訪時,則從棧頂的隊列結點中彈出隊列元素,//將隊列中的結點元素依次出隊,若已訪問,則繼續出隊(當當前隊列結點已空時,//則繼續出棧,彈出下一個棧頂的隊列),直至遇到有未訪問結點(訪問并置當前點為該點)或直到棧為空。 flag=0;while(!isNullSeqStack(testStack)&&!flag){ v1=frontQueueInSt(testStack); //返回棧頂結點的隊列中的隊首元素 deQueueInSt(testStack); //將棧頂結點的隊列中的隊首元素彈出 if(v1->marked==0){ visit(v1);v1->marked=1;inV=v1;flag=1; }}} }while(!isNullSeqStack(testStack));//the algorithm ends when the stack is null }-----------------------------
上述程序的幾點說明:
所以,這里應使用的數據結構的構成方式應該采用下面這種形式:
1)隊列的實現中,每個隊列結點均為圖中的結點指針類型.
定義一個以隊列尾部下標加隊列長度的環形隊列如下:
其余基本操作不再贅述.????
2)堆棧的實現中,每個堆棧中的結點元素均為一個指向隊列的指針,定義如下:
為了提供更好的封裝性,對這個堆棧實現兩種特殊的操作
2.1) deQueueInSt操作用于將棧頂結點的隊列中的隊首元素彈出.
void deQueueInSt(PSeqStack inStack) {if(isEmptyQueue(seqTop(inStack))||isNullSeqStack(inStack)){printf("in deQueueInSt,under flow!/n");return; } deQueue(seqTop(inStack));if(isEmptyQueue(seqTop(inStack)))inStack->slot--; }2.2) frontQueueInSt操作用以返回棧頂結點的隊列中的隊首元素.
QElemType frontQueueInSt(PSeqStack inStack) {if(isEmptyQueue(seqTop(inStack))||isNullSeqStack(inStack)){printf("in frontQueueInSt,under flow!/n");return '/r'; } return getHeadData(seqTop(inStack)); }?
===================
ok,本文完。
總結
以上是生活随笔為你收集整理的BFS和DFS优先搜索算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 机器学习算法之旅
- 下一篇: 漫谈递归:从斐波那契开始了解尾递归