数据结构:图的遍历--深度优先、广度优先

Algorithm and structure 专栏收录该内容
12 篇文章 0 订阅

图的遍历:深度优先、广度优先

遍历

    图的遍历是指从图中的某一顶点出发,按照一定的策略访问图中的每一个顶点。当然,每个顶点有且只能被访问一次。

    在图的遍历中,深度优先和广度优先是最常使用的两种遍历方式。这两种遍历方式对无向图和有向图都是适用的,并且都是从指定的顶点开始遍历的。先看下两种遍历方式的遍历规则:

深度优先

    深度优先遍历也叫深度优先搜索(Depth First Search)。它的遍历规则:不断地沿着顶点的深度方向遍历。顶点的深度方向是指它的邻接点方向。

    具体点,给定一图G=<V,E>,用visited[i]表示顶点i的访问情况,则初始情况下所有的visited[i]都为false。假设从顶点V0开始遍历,则下一个遍历的顶点是V0第一个邻接点Vi,接着遍历Vi第一个邻接点Vj,……直到所有的顶点都被访问过。

    所谓的第一个是指在某种存储结构中(邻接矩阵邻接表),所有邻接点中存储位置最近的,通常指的是下标最小的。在遍历的过程中有两种情况经常出现

  1. 某个顶点的邻接点都已被访问过的情况,此时需回溯已访问过的顶点。
  2. 图不连通,所有的已访问过的顶点都已回溯完了,仍找不出未被访问的顶点。此时需从下标0开始检测visited[i],找到未被访问的顶点i,从i开始新一轮的深度搜索。
看一个例子

从V 0 开始遍历
    遍历分析:V0有两个邻接点V1V2,选择下标最小的V1遍历。接着从V1开始深度遍历,V1只有邻接点V3,也就是没有选的:遍历V3。接着从V3开始遍历,V3只有邻接点V0,而V0已经被遍历过。此时出现了上面提到的情况一,开始回溯V1V1无未被遍历的邻接点,接着回溯V0,V0有一个未被遍历的邻接点V2,新的一轮深度遍历从V2开始。V2无邻接点,且无法回溯。此时出现了情况二,检测visited[i],只有V4了。深度遍历完成。看到回溯,应该可以想到需要使用栈。
遍历序列是
V0->V1->V3->V2->V4
从其它顶点出发的深度优先遍历序列是:
V1->V3->V0->V2->V4
V2->V0->V1->V3->V4
V3->V0->V1->V2->V4
V4->V2->V0->V1->V3
以上结果,我们稍后用于测试程序。

结合在图的实现:邻接矩阵中的代码,我们看下在邻接矩阵形式下的图的深度遍历算法:

深度优先代码

[cpp]  view plain copy
  1. /* 
  2. 深度优先搜索 
  3. 从vertex开始遍历,visit是遍历顶点的函数指针 
  4. */  
  5. void Graph::dfs(int vertex, void (*visit)(int))  
  6. {  
  7.     stack<int> s;  
  8.     //visited[i]用于标记顶点i是否被访问过  
  9.     bool *visited = new bool[numV];  
  10.     //count用于统计已遍历过的顶点数  
  11.     int i, count;  
  12.     for (i = 0; i < numV; i++)  
  13.         visited[i] = false;  
  14.     count = 0;  
  15.     while (count < numV)  
  16.     {  
  17.         visit(vertex);  
  18.         visited[vertex] = true;  
  19.         s.push(vertex);  
  20.         count++;  
  21.         if (count == numV)  
  22.             break;  
  23.         while (visited[vertex])  
  24.         {  
  25.             for (i = 0; i < numV  
  26.                 && (visited[i]   
  27.                 || matrix[vertex][i] == 0 || matrix[vertex][i] == MAXWEIGHT); i++);  
  28.             if (i == numV)  //当前顶点vertex的所有邻接点都已访问完了  
  29.             {  
  30.                 if (!s.empty())  
  31.                 {  
  32.                     s.pop();   //此时vertex正是栈顶,应先出栈  
  33.                     if (!s.empty())  
  34.                     {  
  35.                         vertex = s.top();  
  36.                         s.pop();  
  37.                     }  
  38.                     else  //若栈已空,则需从头开始寻找新的、未访问过的顶点  
  39.                     {  
  40.                         for (vertex = 0; vertex < numV && visited[vertex]; vertex++);  
  41.                     }  
  42.                 }  
  43.                 else  //若栈已空,则需从头开始寻找新的、未访问过的顶点  
  44.                 {  
  45.                     for (vertex = 0; vertex < numV && visited[vertex]; vertex++);  
  46.                 }  
  47.             }  
  48.             else  //找到新的顶点应更新当前访问的顶点vertex  
  49.                 vertex = i;  
  50.         }  
  51.     }  
  52.     delete[]visited;  
  53. }  
其它代码前面已经见过,就不给出了,下面看下图的广度遍历。深度遍历和广度遍历的测试,稍后一并给出。

广度优先

    广度优先遍历也叫广度优先搜索(Breadth First Search)。它的遍历规则:
  1. 先访问完当前顶点的所有邻接点。(应该看得出广度的意思)
  2. 先访问顶点的邻接点先于后访问顶点的邻接点被访问。
    具体点,给定一图G=<V,E>,用visited[i]表示顶点i的访问情况,则初始情况下所有的visited[i]都为false。假设从顶点V0开始遍历,且顶点V0的邻接点下表从小到大有Vi、Vj...Vk。按规则1,接着应遍历Vi、Vj和Vk。再按规则2,接下来应遍历Vi的所有邻接点,之后是Vj的所有邻接点,...,最后是Vk的所有邻接点。接下来就是递归的过程...
在广度遍历的过程中,会出现图不连通的情况,此时也需按上述情况二来进行:测试visited[i]...。 在上述过程中,可以看出需要用到队列。

举个例子,还是同样一幅图:

从V 0 开始遍历
    遍历分析:V0有两个邻接点V1和V2,于是按序遍历V1、V2。V1先于V2被访问,于是V1的邻接点应先于V2的邻接点被访问,那就是接着访问V3。V2无邻接点,只能看V3的邻接点了,而V0已被访问过了。此时需检测visited[i],只有V4了。广度遍历完毕。
遍历序列是
V0->V1->V2->V3->V4
从其它顶点出发的广度优先遍历序列是
V1->V3->V0->V 2 -> V 4
V2->V0->V1->V3->V4
V3->V0->V1->V2->V4
V4->V2->V0->V1->V3
以上结果,我们同样用于测试程序。

在邻接矩阵下,图的广度遍历算法

广度优先代码

[cpp]  view plain copy
  1. /* 
  2. 广度优先搜索 
  3. 从vertex开始遍历,visit是遍历顶点的函数指针 
  4. */  
  5. void Graph::bfs(int vertex, void(*visit)(int))  
  6. {  
  7.     //使用队列  
  8.     queue<int> q;  
  9.     //visited[i]用于标记顶点i是否被访问过  
  10.     bool *visited = new bool[numV];  
  11.     //count用于统计已遍历过的顶点数  
  12.     int i, count;  
  13.     for (i = 0; i < numV; i++)  
  14.         visited[i] = false;  
  15.     q.push(vertex);  
  16.     visit(vertex);  
  17.     visited[vertex] = true;  
  18.     count = 1;  
  19.     while (count < numV)  
  20.     {  
  21.         if (!q.empty())  
  22.         {  
  23.             vertex = q.front();  
  24.             q.pop();  
  25.         }  
  26.         else  
  27.         {  
  28.             for (vertex = 0; vertex < numV && visited[vertex]; vertex++);  
  29.             visit(vertex);  
  30.             visited[vertex] = true;  
  31.             count++;  
  32.             if (count == numV)  
  33.                 return;  
  34.             q.push(vertex);  
  35.         }  
  36.         //代码走到这里,vertex是已经访问过的顶点  
  37.         for (int i = 0; i < numV; i++)  
  38.         {  
  39.             if (!visited[i] && matrix[vertex][i] > 0 && matrix[vertex][i] < MAXWEIGHT)  
  40.             {  
  41.                 visit(i);  
  42.                 visited[i] = true;  
  43.                 count ++;  
  44.                 if (count == numV)  
  45.                     return;  
  46.                 q.push(i);  
  47.             }  
  48.         }  
  49.     }  
  50.     delete[]visited;  
  51. }  

结合两种遍历的代码,我们对同一幅图进行测试,它的主函数是
[cpp]  view plain copy
  1. void visit(int vertex)  
  2. {  
  3.     cout << setw(4) << vertex;  
  4. }  
  5. int main()  
  6. {  
  7.     cout << "******图的遍历:深度优先、广度优先***by David***" << endl;  
  8.     bool isDirected, isWeighted;  
  9.     int numV;  
  10.     cout << "建图" << endl;  
  11.     cout << "输入顶点数 ";  
  12.     cin >> numV;  
  13.     cout << "边是否带权值,0(不带) or 1(带) ";  
  14.     cin >> isWeighted;  
  15.     cout << "是否是有向图,0(无向) or 1(有向) ";  
  16.     cin >> isDirected;  
  17.     Graph graph(numV, isWeighted, isDirected);  
  18.     cout << "这是一个";  
  19.     isDirected ? cout << "有向、" : cout << "无向、";  
  20.     isWeighted ? cout << "有权图" << endl : cout << "无权图" << endl;  
  21.     graph.createGraph();  
  22.     cout << "打印邻接矩阵" << endl;  
  23.     graph.printAdjacentMatrix();  
  24.     cout << endl;  
  25.     cout << "深度遍历" << endl;  
  26.     for (int i = 0; i < numV; i++)  
  27.     {  
  28.         graph.dfs(i, visit);  
  29.         cout << endl;  
  30.     }  
  31.     cout << endl;  
  32.     cout << "广度遍历" << endl;  
  33.     for (int i = 0; i < numV; i++)  
  34.     {  
  35.         graph.bfs(i, visit);  
  36.         cout << endl;  
  37.     }  
  38.     system("pause");  
  39.     return 0;  
  40. }  

运行



仔细对照测试结果,我们的代码是没有问题的。


小结

对于某个图来说,深度优先遍历和广度优先遍历的序列不是唯一的,但当图的存储结构一确定,它的遍历序列就是唯一的。因为当有多个候选点时,我们总是优先选择下标最小的。
深度遍历:其实质是运用了递归思想,在遍历图中时,对图中的每个顶点之多调用一次DNS函数,因为一旦某个顶点把标志成已被访问,就不再从它出发进行搜索了,因此遍历图的实质上是对每个顶点查找其邻接点的过程。

广度遍历:对于图的广度优先遍历的来说,运用了队列的特点,每一个顶点之多进一次队列,遍历图的实质上是通过边或者弧
找邻接点的过程。

从上可以看出,其实广度遍历和深度遍历它们两者的时间复杂度是一样的,两者的不同之处仅仅在于对顶点访问的顺序不同而已。


  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值