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

和树的遍历类似,在此,我们希望从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历(TraversingGraph)。如果只访问图的顶点而不关注边的信息,那么图的遍历十分简单,使用一个foreach语句遍历存放顶点信息的数组即可。但如果为了实现特定算法,就需要根据边的信息按照一定顺序进行遍历。图的遍历算法是求解图的连通性问题、拓扑排序和求关键路径等算法的基础。
图的遍历要比树的遍历复杂得多,由于图的任一顶点都可能和其余顶点相邻接,故在访问了某顶点之后,可能顺着某条边又访问到了已访问过的顶点,因此,在图的遍历过程中,必须记下每个访问过的顶点,以免同一个顶点被访问多次。为此给顶点附设访问标志visited,其初值为false,一旦某个顶点被访问,则其visited标志置为true。

图的遍历方法有两种:

  1. 深度优先搜索遍历(Depth-First Search 简称DFS)。
  2. 广度优先搜索遍历(Breadth_First Search 简称BFS)。

注意:

  1. 如果给你一个图形,然后让你遍历,即使告诉你第一个起点,也不能得到一个唯一的遍历序列。
  2. 如果给的是一个图的存储结构,然后告诉你访问的其实顶点,那遍历序列唯一。
  3. 深度优先遍历借助于栈的数据结构,广度优先遍历借助于队列的数据结构。

一,深度优先搜索遍历:

图的深度优先搜索遍历类似于二叉树的深度优先搜索遍历,并且借助于栈的数据结构。其基本思想如下:假定以图中某个顶点Vi为出发点,首先访问出发点,然后选择一个Vi的未访问过的邻接点Vj,以Vj为新的出发点继续进行深度优先搜索,直至图中所有顶点都被访问过。显然,这是一个递归的搜索过程。
现以图8.15为例说明深度优先搜索过程。假定V1是出发点,首先访问V1。因V1有两个邻接点V2、V3均末被访问过,可以选择V2作为新的出发点,访问V2之后,再找V2的末访问过的邻接点。同V2邻接的有V1、V4和V5,其中V1已被访问过,而V4、V5尚未被访问过,可以选择V4作为新的出发点。重复上述搜索过程,继续依次访问V8、V5 。访问V5之后,由于与V5相邻的顶点均已被访问过,搜索退回到V8,访问V8的另一个邻接点V6。接下来依次访问V3和V7,最后得到的的顶点的访问序列为:V1 → V2 → V4 → V8 → V5 → V6 → V3 → V7。
《图:图的遍历(深度优先遍历、广度优先遍历)》
下面根据上一节创建的邻接表存储结构添加深度优先搜索遍历代码。
【例8-2 DFSTraverse.cs】深度优先搜索遍历

打开【例8-1  AdjacencyList.cs】,在AdjacencyList<T>类中添加以下代码后,将文件另存为DFSTraverse.cs。

public void DFSTraverse(){ //深度优先遍历
    InitVisited(); //将visited标志全部置为false
    DFS(items[0]); //从第一个顶点开始遍历
}

private void DFS(Vertex<T> v){ //使用递归进行深度优先遍历
    v.visited = true; //将访问标志设为true
    Console.Write(v.data + " "); //访问
    Node node = v.firstEdge;
    while (node != null){ //访问此顶点的所有邻接点
       //如果邻接点未被访问,则递归访问它的边
        if (!node.adjvex.visited){
            DFS(node.adjvex); //递归
        }
        node = node.next; //访问下一个邻接点
    }
}

private void InitVisited(){ //初始化visited标志
    foreach (Vertex<T> v in items){
    v.visited = false; //全部置为false
    }
}
【例8-2  Demo8-2.cs】深度优先搜索遍历测试
using System;
class Demo8_2{
    static void Main(string[] args){
        AdjacencyList<string> a = new AdjacencyList<string>();
        a.AddVertex("V1");
        a.AddVertex("V2");
        a.AddVertex("V3");
        a.AddVertex("V4");
        a.AddVertex("V5");
        a.AddVertex("V6");
        a.AddVertex("V7");
        a.AddVertex("V8");
        a.AddEdge("V1", "V2");
        a.AddEdge("V1", "V3");
        a.AddEdge("V2", "V4");
        a.AddEdge("V2", "V5");
        a.AddEdge("V3", "V6");
        a.AddEdge("V3", "V7");
        a.AddEdge("V4", "V8");
        a.AddEdge("V5", "V8");
        a.AddEdge("V6", "V8");
        a.AddEdge("V7", "V8");
        a.DFSTraverse();
    }
}

运行结果: V1 V2 V4 V8 V5 V6 V3 V7

二:广度优先搜索遍历

图的广度优先搜索遍历算法是一个分层遍历的过程,和二叉树的广度优先搜索遍历类同,借助于队列的数据结构。。它从图的某一顶点Vi出发,访问此顶点后,依次访问Vi的各个未曾访问过的邻接点,然后分别从这些邻接点出发,直至图中所有已有已被访问的顶点的邻接点都被访问到。对于图8.15所示的无向连通图,若顶点Vi为初始访问的顶点,则广度优先搜索遍历顶点访问顺序是:V1 → V2 → V3 → V4 → V5 → V6 → V7 → V8。遍历过程如图8.16的所示。
《图:图的遍历(深度优先遍历、广度优先遍历)》

和二叉树的广度优先搜索遍历类似,图的广度优先搜索遍历也需要借助队列来完成,例8.3演示了这个过程。

打开【例8-2  DFSTraverse.cs】,在AdjacencyList<T>类中添加以下代码后,将文件另存为BFSTraverse.cs。

public void BFSTraverse(){ //广度优先遍历

    InitVisited(); //将visited标志全部置为false
    BFS(items[0]); //从第一个顶点开始遍历
}

private void BFS(Vertex<T> v) {//使用队列进行广度优先遍历
   //创建一个队列
    Queue<Vertex<T>> queue = new Queue<Vertex<T>>();
    Console.Write(v.data + " "); //访问
    v.visited = true; //设置访问标志
    queue.Enqueue(v); //进队
    while (queue.Count > 0){ //只要队不为空就循环
        Vertex<T> w = queue.Dequeue();
        Node node = w.firstEdge;
        while (node != null){ //访问此顶点的所有邻接点
           //如果邻接点未被访问,则递归访问它的边
            if (!node.adjvex.visited){
                Console.Write(node.adjvex.data + " "); //访问
                node.adjvex.visited = true; //设置访问标志
                queue.Enqueue(node.adjvex); //进队
            }
            node = node.next; //访问下一个邻接点
        }
    }
}
using System;
class Demo8_3
{
    static void Main(string[] args)
    {
        AdjacencyList<string> a = new AdjacencyList<string>();
        a.AddVertex("V1");
        a.AddVertex("V2");
        a.AddVertex("V3");
        a.AddVertex("V4");
        a.AddVertex("V5");
        a.AddVertex("V6");
        a.AddVertex("V7");
        a.AddVertex("V8");
        a.AddEdge("V1", "V2");
        a.AddEdge("V1", "V3");
        a.AddEdge("V2", "V4");
        a.AddEdge("V2", "V5");
        a.AddEdge("V3", "V6");
        a.AddEdge("V3", "V7");
        a.AddEdge("V4", "V8");
        a.AddEdge("V5", "V8");
        a.AddEdge("V6", "V8");
        a.AddEdge("V7", "V8");
        a.BFSTraverse(); //广度优先搜索遍历
    }
}

运行结果: V1 V2 V3 V4 V5 V6 V7 V8

非连通图的遍历:

以上讨论的图的两种遍历方法都是相对于无向连通图的,它们都是从一个顶点出发就能访问到图中的所有顶点。若无向图是非连通图,则只能访问到初始点所在连通分量中的所有顶点,其他连通分量中的顶点是不可能访问到的(如图8.17所示)。为此需要从其他每个连通分量中选择初始点,分别进行遍历,才能够访问到图中的所有顶点,否则不能访问到所有顶点。为此同样需要再选初始点,继续进行遍历,直到图中的所有顶点都被访问过为止。
《图:图的遍历(深度优先遍历、广度优先遍历)》

public void DFSTraverse() //深度优先遍历
    {
        InitVisited(); //将visited标志全部置为false
        foreach (Vertex<T> v in items)
        {
            if (!v.visited) //如果未被访问
            {
                DFS(v); //深度优先遍历
            }
        }
    }
    public void BFSTraverse() //广度优先遍历
    {
        InitVisited(); //将visited标志全部置为false
        foreach (Vertex<T> v in items)
        {
            if (!v.visited) //如果未被访问
            {
                BFS(v); //广度优先遍历
            }
        }
    }

深度遍历、广度遍历的时间复杂度:

  1. 在邻接矩阵上遍历,一般至少需要将矩阵中元素一半给过一下,由于矩阵元素个数为 n2 ,因此时间复杂度就是 O(n2)
  2. 至于在邻接表上遍历时,过程与这个类似,但是邻接表中只是存储了边结点(e条边,无向图也只是2e个结点),加上表头结点为n(也就是顶点个数),因此时间复杂度为O(n+e)。
  3. 另外,在邻接表中判断某个顶点是否关联,最坏时可能需要将链表中所有结点都遍历完(尤其是有向图中),此时时间复杂度自然就是O(e)了。
    原文作者:数据结构之图
    原文地址: https://blog.csdn.net/qq_25508039/article/details/75219529
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞