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

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

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

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

  • 相关阅读:
    用“Keras”11行代码构建CNN
    技术 | 使用深度学习检测DGA(域名生成算法)
    未来的超级智能网络攻击需要AI竞技俱乐部来拯救
    开源中国的代码托管
    Hello Java !
    15-include的使用
    14-递归函数
    13-函数的调用
    12-函数的返回值
    11-函数的参数
  • 原文地址:https://www.cnblogs.com/mrlonely2018/p/11932980.html
Copyright © 2020-2023  润新知