教你通透徹底理解:BFS和DFS優先搜尋演算法

來源:互聯網
上載者:User

作者:July  二零一一年一月一日

---------------------------------

本人蔘考:演算法導論
本人聲明:個人原創,轉載請註明出處。

ok,開始。

翻遍網上,關於此類BFS和DFS演算法的文章,很多。但,都說不出個所以然來。
讀完此文,我想,
你對圖的廣度優先搜尋和深度優先搜尋定會有個通通透透,徹徹底底的認識。

---------------------

咱們由BFS開始:
首先,看下演算法導論一書關於 此BFS 廣度優先搜尋演算法的概述。
演算法導論第二版,中譯本,第324頁。
廣度優先搜尋(BFS)
在Prime最小產生樹演算法,和Dijkstra單源最短路徑演算法中,都採用了與BFS 演算法類似的思想。

//u 為 v 的先輩或父母。
BFS(G, s)
 1  for each vertex u ∈ V [G] - {s}
 2       do color[u] ← WHITE
 3          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 遞迴實現:
void dftR(PGraphMatrix inGraph)
{
       PVexType v;
       assertF(inGraph!=NULL,"in dftR, pass in inGraph is null/n");
       printf("/n===start of dft recursive version===/n");
       for(v=firstVertex(inGraph);v!=NULL;v=nextVertex(inGraph,v))
              if(v->marked==0)
                     dfsR(inGraph,v);
       printf("/n===end of   dft recursive version===/n");
}

void dfsR(PGraphMatrix inGraph,PVexType inV)
{
       PVexType v1;
       assertF(inGraph!=NULL,"in dfsR,inGraph is null/n");
       assertF(inV!=NULL,"in dfsR,inV is null/n");
       inV->marked=1;
       visit(inV);
       for(v1=firstAdjacent(inGraph,inV);v1!=NULL;v1=nextAdjacent(inGraph,inV,v1))
       //v1當為v的鄰接點。
              if(v1->marked==0)
                     dfsR(inGraph,v1);
}

 

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)隊列的實現中,每個隊列結點均為圖中的結點指標類型.
定義一個以隊列尾部下標加隊列長度的環形隊列如下:

struct SingleRearSeqQueue;
typedef PVexType   QElemType;
typedef struct SingleRearSeqQueue* PSingleRearSeqQueue;
struct SingleRearSeqQueue
{
 int rear;
 int quelen;
 QElemType dataPool[MAXNUM];
};
其餘基本操作不再贅述.    

2)堆棧的實現中,每個堆棧中的結點元素均為一個指向隊列的指標,定義如下:
#define SEQ_STACK_LEN 1000
#define StackElemType PSingleRearSeqQueue
struct SeqStack;
typedef struct SeqStack* PSeqStack;
struct SeqStack
{
 StackElemType dataArea[SEQ_STACK_LEN];
 int slot;
};
為了提供更好的封裝性,對這個堆棧實現兩種特殊的操作

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,本文完。

            July、二零一一年一月一日。Happy 2011 new year!

作者聲明:
本人July對本部落格所有任何內容和資料享有著作權,轉載請註明作者本人July及出處。
永遠,向您的厚道致敬。謝謝。July、二零一零年十二月二日。

 

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/v_JULY_v/archive/2011/01/01/6111353.aspx

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.