圖的數據結構為鄰接鏈表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已經沒有未訪問的鄰接頂點時,退出本次遞歸(相當於非遞歸中的頂點出棧)。
輸出示例:(由於遞歸,輸出有點難看)
總結:編程、尤其是算法,是需要經常訓練的····一切紙上談兵都是沒用的。另外對於這些圖的算法,腦子里想不清楚建議拿出紙筆,把一步步的步驟寫出來、畫出來,會提高效率。寫下這篇博客,加深記憶。
文中如有謬誤,敬請讀者指出,謝謝!