BFS和DFS分別用於有向圖和無向圖的一點心得和總結


圖的數據結構為鄰接鏈表adjacency list。

listVertex是一個儲存Vertex* 頂點類指針的vector類型的STL;在Vertex類中有一個類成員nextEdgeNode,他是儲存pair<int,int>類型的vector容器;數對pair的first表示邊指向的頂點序號,second表示邊的序號。

四個函數都只有一個參數,在聲明時提供了默認的參數值-1;參數表示起始頂點的編號

先放一個測試用的圖、有向圖的鄰接鏈表、無向圖的鄰接鏈表。

以下是有向圖的BFS

 1 void Graph::BFSListO(int startFrom) {
 2     /*the principle is to firstly choose a random vertex, if it has 0 out degree, then choose the first
 3         unvisited vertex in the list; moreover,if one way is exhausted to continue,also push the first univisited vertex
 4         in the list to the queue*/
 5     cout << "BFS of oriented Adjacey List:" << endl;
 6     srand((unsigned int)time(NULL));
 7     int sze = listVertex.size();
 8     int rdm = rand() % sze ;//[0,sze)
 9     while (listVertex[rdm]->nextEdgeNode.empty()) {
10         rdm = rand() % sze;//randomly choose a vertex whose out degree is not 0
11     }
12     queue<Vertex*> myQueue;
13     if(startFrom==-1)//default treatment is random choose
14     myQueue.push(listVertex[rdm]);
15     else {//designated choose
16         myQueue.push(listVertex[startFrom-1]);
17     }
18     while (!myQueue.empty()) {// travel through until there is nothing in the queue
19         Vertex* V = myQueue.front();
20         if (V->color == 0) {
21             V->color = 1; //O black(unvisited) & 1 white(visited)//V visited white 1
22             cout << "Vertex visited:" << V->id +1<< endl;
23         }
24         if (!V->nextEdgeNode.empty()) {//if the out degree is not zero
25             for (auto it = V->nextEdgeNode.begin(); it != V->nextEdgeNode.end(); it++) {//travel through all outgoing vertices
26                 if(listVertex[(*it).first - 1]->color==0)
27                 myQueue.push(listVertex[(*it).first-1]);//push into queue for next round travel
28             }
29         }
30         else {//if the first chosed vertex has zero out degree, then add the first unvisited vertex from list
31             for (auto it = listVertex.begin(); it != listVertex.end(); it++) {
32                 if ((*it)->color == 0) {//if unvisited
33                     myQueue.push(*it);
34                     break;/*ATTENTION without this line the algo will be wrong !
35                     because more than one unvisited vertex has been added to queue,
36                         which causes a sequential fault*/
37                 }
38             }
39         }
40         myQueue.pop();
41         
42     }
43 }
其實有向圖比無向圖復雜得多。19行以前都在初始化,把第一個頂點放入隊列。這里使用FIFO隊列進行輔助。每一次循環,總是先訪問隊頭頂點;然后從這個頂點開始遍歷(不是訪問)他的所有子頂點,將他們加入隊尾;最后把這個元素彈出隊列。
這里需要注意有向圖的特殊之處:有的頂點可能出度為零,因此需要加入26和32行的判斷語句,如果被訪問了的這個頂點沒有子頂點,則按順序選擇一個未被訪問過的頂點,加入隊尾(注意只能選擇一個哦,不要忘記break)
輸出示例:

 

 以下是無向圖的BFS

 1 void Graph::BFSListN(int startFrom) {
 2     /*the principle is to firstly choose a random vertex, if it has 0 out degree, then choose the first
 3         unvisited vertex in the list; moreover,if one way is exhausted to continue,also push the first univisited vertex
 4         in the list to the queue*/
 5     cout << "BFS of non-oriented Adjacey List:" << endl;
 6     srand((unsigned int)time(NULL));
 7     int sze = listVertex.size();
 8     int rdm = rand() % sze;//[0,sze)
 9     while (listVertex[rdm]->nextEdgeNode.empty()) {
10         rdm = rand() % sze;//randomly choose a vertex whose out degree is not 0
11     }
12     queue<Vertex*> myQueue;
13     if (startFrom == -1) {//default treatment is random choose
14         myQueue.push(listVertex[rdm]);
15         listVertex[rdm]->color = 2;
16     }
17     else {//designated choose
18         myQueue.push(listVertex[startFrom - 1]);
19         listVertex[startFrom - 1]->color = 2;
20     }
21     while (!myQueue.empty()) {// travel through until there is nothing in the queue
22         Vertex* V = myQueue.front();
23         V->color = 1; //O black(unvisited) & 1 white(visited) & 2 gray(now in queue)//V visited white 1
24         cout << "Vertex visited:" << V->id + 1 << endl;
25             for (auto it = V->nextEdgeNode.begin(); it != V->nextEdgeNode.end(); it++) {//travel through all outgoing vertices
26                 if (listVertex[(*it).first - 1]->color !=1&& listVertex[(*it).first - 1]->color !=2){
27                     myQueue.push(listVertex[(*it).first - 1]);//push into queue for next round travel
28                     listVertex[(*it).first - 1]->color = 2;
29                 }
30             }
31         myQueue.pop();
32     }
33 }

無向圖要簡單多了,關鍵看兩個不同之處,一是這里設置了三個顏色(23行),二是while中的內容簡潔得多。

至於為什么這里要設置三種狀態(未訪問0、已訪問1、已入隊列2),主要是這里的算法如果用兩種狀態會有問題。假設兩種狀態(0未訪問、1已訪問),如果從7開始訪問(置1),好,3、4、5依次加入隊尾(無狀態設置);然后7被彈出,訪問3(置1),並把3的所有非1(未訪問)的子頂點4、6加入隊尾,這里就發現4被重復加入了隊列一次,這是不正確的。

while中的邏輯就很簡單,每次訪問完某頂點之后,把其所有未被訪問過、且不在隊列中的頂點加入隊尾就行了。

輸出示例:

以下是有向圖的非遞歸DFS

 1 void Graph::DFSListO(int startFrom) {
 2     cout << "Non recursive DFS of oriented Adjacey List:" << endl;
 3     srand((unsigned int)time(NULL));
 4     int sze = listVertex.size();
 5     int rdm = rand() % sze;//[0,sze)
 6     while (listVertex[rdm]->nextEdgeNode.empty()) {
 7         rdm = rand() % sze;//randomly choose a vertex whose out degree is not 0
 8     }
 9     stack<Vertex*> myStack;
10     Vertex* tempV;
11     int countVisitedAll = 0;//to control overall number of visited vertices
12     if (startFrom == -1) {
13         tempV = listVertex[rdm];//randomly chosed
14     }
15     else
16     {
17         tempV = listVertex[startFrom - 1];//designated
18     }
19     tempV->color = 1;//O black(unvisited) & 1 white(visited)
20     cout << "Vertex visited:" << tempV->id + 1 << endl;
21     myStack.push(tempV);
22     countVisitedAll++;
23     while (!myStack.empty()) {
24         tempV = myStack.top();
25             int countVisited = 0;//to control number of visited vertices of one certain vertex
26             for (auto it = tempV->nextEdgeNode.begin(); it != tempV->nextEdgeNode.end(); it++) {
27                 if (listVertex[(*it).first-1]->color==0) {//if an unvisited vertex has been found
28                     listVertex[(*it).first - 1]->color = 1;//O black(unvisited) & 1 white(visited)
29                     cout << "Vertex visited:" << listVertex[(*it).first - 1]->id + 1 << endl;
30                     myStack.push(listVertex[(*it).first - 1]);//visit it and push into stack
31                     countVisitedAll++;
32                     break;
33                 }
34                 else//if a visited vertex found
35                 {
36                     countVisited++;//then increment the count
37                 }
38             }
39             if (countVisited == tempV->nextEdgeNode.size()) {
40                 myStack.pop();
41             }
42             if (countVisitedAll != listVertex.size() && myStack.empty()) {
43                 for (auto it = listVertex.begin(); it != listVertex.end(); it++)
44                     if ((*it)->color == 0) {
45                         (*it)->color = 1;//O black(unvisited) & 1 white(visited)
46                         cout << "Vertex visited:" << (*it)->id+ 1 << endl;
47                         countVisitedAll++;
48                         myStack.push(*it);
49                         break;
50                     }
51             }
52         }
53     }

使用棧作為輔助數據結構,所有的頂點在訪問后被壓入棧中。當當前頂點的所有鄰接頂點都已被訪問時,彈出當前頂點(39-40行);當其還有鄰接頂點未被訪問時,則從第一個未被訪問的頂點開始訪問,並將其入棧(26-32行,注意break)。另外對於有向圖DFS要考慮到一種情況,就是非強連通圖(測試用圖就是非強連通圖)的問題:如果從初始選擇的頂點開始進行DFS,在遍歷完他所在的連通分量之后,棧中所有元素都將被彈出,這時如果不作處理,遍歷就會結束,這是錯誤的(如圖中從4開始的話,訪問完4、7、5就結束)。42-49行做的,就是在訪問完其中一個連通分量后,按順序選擇一個未訪問過的頂點,訪問之,並入棧。這樣保證了算法的通用性。

輸出示例:

 最后一個是遞歸的無向圖DFS

 1 void Graph::DFSListN(int startFrom) {
 2     cout << "Recursive DFS of non-oriented Adjacey List:" << endl;
 3     srand((unsigned int)time(NULL));
 4     int sze = listVertex.size();
 5     int rdm = rand() % sze;//[0,sze)
 6     while (listVertex[rdm]->nextEdgeNode.empty()) {
 7         rdm = rand() % sze;//randomly choose a vertex whose out degree is not 0
 8     }
 9     Vertex* tempV;
10     if (startFrom == -1)
11         tempV = listVertex[rdm];
12     else
13         tempV = listVertex[startFrom - 1];
14     if (tempV->color == 0) {//not visited
15         tempV->color = 1;//O black(unvisited) & 1 white(visited)
16         cout << "Vertex visited:" << tempV->id+1 << endl;
17         for (auto it = tempV->nextEdgeNode.begin(); it != tempV->nextEdgeNode.end(); it++) {
18             if (listVertex[(*it).first - 1]->color == 0) {
19                 DFSListN((*it).first);
20             }
21         }
22     }
23 }

無向圖的遞歸真的很簡單哦,每次遞歸時,tempV都指向前一次訪問的頂點的第一個未訪問鄰接頂點;當tempV已經沒有未訪問的鄰接頂點時,退出本次遞歸(相當於非遞歸中的頂點出棧)。

輸出示例:(由於遞歸,輸出有點難看)

 

 總結:編程、尤其是算法,是需要經常訓練的····一切紙上談兵都是沒用的。另外對於這些圖的算法,腦子里想不清楚建議拿出紙筆,把一步步的步驟寫出來、畫出來,會提高效率。寫下這篇博客,加深記憶。

文中如有謬誤,敬請讀者指出,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM