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已经没有未访问的邻接顶点时,退出本次递归(相当于非递归中的顶点出栈)。

输出示例:(由于递归,输出有点难看)

 总结:编程、尤其是算法,是需要经常训练的····一切纸上谈兵都是没用的。另外对于这些图的算法,脑子里想不清楚建议拿出纸笔,把一步步的步骤写出来、画出来,会提高效率。写下这篇博客,加深记忆。

文中如有谬误,敬请读者指出,谢谢!

原文地址:https://www.cnblogs.com/mrlonely2018/p/11932980.html