深度優先演算法和廣度優先演算法
今天做了道題目,《手機鍵盤輸入》當按下23時,輸出["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]。
其實說白了,也就是全排列問題,將2代表的abc,和3代表的def輸出組合的字元。
我是按照普通方法,遞迴來寫的,覺得這道題目也只是考驗程式設計,考驗遞迴的。沒太多考慮,當上網看別人都提到DFS(深度優先演算法)後,才意識到,是有章法可循的。以前只以為深度優先和廣度優先只是在圖的遍歷的時候才能用到,想不到這些也只是工具,看你怎麼去應用到你的演算法裡面。
好了,不多說了,基於上述原因,參考《資料結構》和《演算法導論》將廣度優先和深度優先總結一下。
這兩個演算法的共同點,都是要有標記!資料結構採用的是一個數組來標記每一個結點是否被遍歷;演算法導論是在結點中附設一個顏色值,來表示遍歷的程度。其實質是一樣的,都需要藉助於標記,來實現。但在二叉樹的遍歷中就不需要了,因為不會形成迴路。
一:深度優先演算法
方案一:“資料結構”
其實這是樹的先根遍歷的擴充套件。
通過圖(b)我們可以看到,v1-v4-v8-v5-v3-v6-v7。
只要遍歷到某一個結點,有子節點就先遍歷子節點,當“子樹”遍歷完了以後,再遍歷另一個“子樹”,只是由於存在迴路,所以遍歷另一個子樹的時候,會訪問已經訪問過的點, 所以就需要標記來判斷。
程式碼如下:
Boolean visited[MAX]; //設定標誌陣列 Status (*VisitFunc)(int v); //全域性變數函式,方便在DFS中呼叫DFSTraverse中傳進的引數 void DFSTraverse(Graph G,Status (*Visit)(int v)) { VisitFunc = Visit; for(v = 0;v < G.vexnum;++v) //初始化,將標誌陣列全設為FALSE visited[v] = FALSE; for(v = 0;v < G.vexnum;++v) //對所有的結點進行遍歷,找到切入點,就呼叫DFS進行深度優先遍歷 if(!visited[v]) DFS(G,v); } void DFS(Graph G,int v) { visited[v] = TRUE; //給第v個結點標記,並訪問該結點。 VisitFunc(v); for(w = FirstAdjVex(G,v); w >= 0;w = NextAdjVex(G,v,w)) //對v的所有子節點w進行深度優先遍歷 if(!visited[w]) DFS(G,w); }
說明:
1:這個題目寫的很簡化,在visited[v]中v是一個確定的數值,表示陣列下標。但在VisitFunc(v)中,v為結點。雖然程式寫法簡化,但更能突出我們要解決的問題。
2:其實DFS和二叉樹的先序遍歷很相像:
visit(p);
PreOrderTraverse(p->left);
PreOrderTraverse(p->right);
其實就是將二叉樹的確定性,轉化成圖子節點的不確定性,通過for迴圈來實現。
3:DFS可以應用在樹的先序遍歷當中。
方案二:“演算法導論”
1:遍歷過程
只要可能,就在圖中儘量深入,總是對最近發現的結點v的出發邊進行探索,直到該結點的所有出發邊都被發現為止。然後回溯到v的父節點(可能有多個,但之前遍歷過程中,從哪個點過來的,就是v的父節點),然後搜尋該前驅結點的出發邊。知道從源節點可以達到的所有的出發邊都遍歷完。如果在圖中還有沒被遍歷的結點, 任選一個,重複上述過程。直到所有的結點遍歷完為止。
2:當發現一個結點v時,記錄其前驅結點u,並設定父節點指標v.π = u; 同樣設定三種顏色,白色表示尚未被遍歷的結點,灰色表示已經被遍歷,但其子節點還沒有被遍歷完全,黑色表示該結點以及所有的子節點都被遍歷完全,要返回到父節點。
3:給每個結點設定時間戳。v.d記錄第一次被發現的時間,v.f記錄對v掃描完成時的時間戳。其中time為全域性變數
程式碼:
DFS(G)
{
for(u = 0;u < G.vexnum;++u){ //初始化所有的結點資訊,父指標設為空,顏色全為白色
u.color = WHITE;
u.π = NULL;
}
time = 0;
for(u = 0;u < G.vexnum;++u) //開始遍歷所有的結點,以防存在多個不相接的圖
if(u.color == WHITE)
DFS_VISIT(G,u);
}
DFS_VISIT(G,u){
time = time + 1; //每次賦值前都要增加1,time為全域性變數
u.d = time;
u.color = GRAY; //當訪問了該結點,就將該節點設為灰色,接下來遍歷其子節點。其實不要該灰色指示也行
for(w = FirstAdjVex(G,v); w >= 0;w = NextAdjVex(G,v,w)) //處理子節點
if(w.color == WHITE){
w.π = u;
DFS_VISIT(G,w);
}
u.color = BLACK;
time = time + 1;
u.f = time;
}
生成的圖
性質:
1、其生成的前驅製圖G形成一個由多棵樹所構成的森林,這是因為深度優先樹的結構與DFS_VISIT的遞迴呼叫結構完全對應。
2、結點的發現時間和完成時間具有括號化結構。也就是隻要兩個結點的時間有重疊,畢竟一個結點的時間段包含在另一個結點的時間段內。
二:廣度優先搜尋
類似於樹的按層次遍歷的過程。
方案一:“資料結構”
先訪問根結點,然後訪問與跟相連的所有結點v1-vn,然後訪問與v1-vn直接相連的所有結點(除去已經訪問過的結點)。這就要用佇列來時間:先訪問某結點,然後將該結點如佇列。從隊列出結點,然後依次訪問該結點的所有子節點(除去已經訪問過的結點),並將這些剛剛訪問結點入佇列(這樣將提前訪問過的結點就不用入隊列了,其子節點已經提前放入隊列當中)。
訪問過程是:v1-v2-v3-v4-v5-v6-v7-v8。
void BFSTraverse(Graph G,Status(*Visit)(int v))
{
for(u = 0;u < G.vexnum;++u) //初始化
visited[u] = FALSE;
InitQueue(Q);
for(v = 0;v < G.vexnum;++v){ //遍歷所有的結點
if(!visited[v]){
visited[v] = TRUE;
Visit(v); //先訪問再入佇列
EnQueue(Q,v);
while(!QueueEmpty(Q)){
DeQueue(Q,u);
for(w = FirstAdjVex(G,v); w >= 0;w = NextAdjVex(G,v,w)){
if(!visited[w]){
visited[w] = TRUE;
Visit(w);
EnQueue(Q,w);
}
}
}
}
}
}
方案二:“演算法導論”
該演算法可以得到最短路徑。
BFS(G,s)
{
for(u = 0;u < G.vexnum;++u){
u.color = WHITE;
u.d = 10000;
u.π = NULL;
}
s.color = GRAY;
s.d = 0;
s.π = NULL;
Q = 空
EnQueue(Q,s);
while(!IsEmpty(Q)){
u = DeQueue(Q);
for(w = FirstAdjVex(G,v); w >= 0;w = NextAdjVex(G,v,w)){
if(w.color == WHITE){
w.color = GRAY;
w.d = u.d + 1;
w.π = u;
EnQueue(Q,w);
}
}
u.color = BLACK;
}
}
遍歷後的圖形:
因為我們有設定父節點指標,當通過廣度優先遍歷之後,我們從要找的葉節點反向遍歷到根結點,就能得到最短的路徑。
PRINT_PATH(G,s,v)
{
if(v == s)
print s;
else if(v.π == NULL){
print "no path form"s"to"v
}else{
PRINT_PATH(G,s,v.π)
print v;
}
}