ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

图的遍历

2022-02-10 13:04:53  阅读:143  来源: 互联网

标签:遍历 访问 算法 搜索 邻接 顶点


6.5 图的遍历

​ 和树的遍历类似,图的遍历也是从图中某一顶点出发,按照某种方法对图中所有顶点访问且仅访问一次。 图的遍历算法是求解图的连通性问题、 拓扑排序和关键路径等算法的基础。
​ 然而,图的遍历要比树的遍历复杂得多。 因为图的任一顶点都可能和其余的顶点相邻接。 所 以在访问了某个顶点之后,可能沿着某条路径搜索之后,又回到该顶点上。 例如,图6.1(b)中 所示的 G2, 由于图中存在回路, 因此在访问了 v1 、 v2 、 v3 、 v4 之后,沿着边<v4, v1>又 可访问到 V1。 为了避免同一顶点被访问多次,在遍历图的过程中,必须记下每个已访问过的顶点。 为此,设一 个辅助数组visited[n] , 其初始值置为"false"或者0, 一 旦访问了顶点 Vi, 便置visited[i]为"true"
或者 1。
​ 根据搜索路径的方向,通常有两条遍历图的路径:深度优先搜索和广度优先搜索。 它们对无向图和有向图都适用。

6.5.1 深度优先搜索

1.深度优先搜索遍历的过程

(1)从图中某个顶点v出发, 访问v。

(2)找出刚访问过的顶点的第一个未被访问的邻接点,访问该顶点。 以该顶点为新顶点,重 复此步骤, 直至刚访问过的顶点没有未被访问的邻接点为止。

(3)返回前一个访问过的且仍有未被访问的邻接点的顶点,找出该顶点的下一个未被访问的 邻接点, 访问该顶点。

(4)重复步骤 (2) 和(3), 直至图中所有顶点都被访问过,搜索结束。

2.深度优先搜索遍历的算法实现

算法6.3 深度优先搜索遍历连通图

  • 算法描述:

    bool visited[MVNum);            //访问标志数组,其初值为 "false"
    void DFS(Graph G,int v)
    {//从第 v 个顶点出发递归地深度优先遍历图G
        cout<<v;visited[v)=true;    //访问第v个顶点,并置访问标志数组相应分扯值为
        for(w=FirstAdjVex(G,v);w>=O;w=NextAdjVex(G,v,w))
        //依次检查v的所有邻接点w , FirstAdjVex(G, v)表示v的第一个邻接点
        //NextAdjVex(G,v,w)表示v相对千w的下一个邻接点, w匀表示存在邻接点
        if(!visited[w]) DFS(G,w);   //对v的尚未访问的邻接顶点 w 递归调用 DFS
    

算法6.4 深度优先搜索遍历非连通图

  • 算法描述:

    void DFSTraverse(Graph G)
    {//对非连通图G做深度优先遍历
        for(v=O;v<G.vexnum;++v) visited[v]=false;
        for(v= O;v<G.vexnum;++v)
            if(!visited[v]) DFS(G,v);
    }
    

算法6.5 采用邻接表表示图的深度优先搜索遍历

  • 算法描述:

    void DFS_AM(AMGraph G,int v)
    {//图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
        cout<<v;visited[v]=true;   //访问第v个顶点,并置访问标志数组相应分址值为true
        for(w=O;w<G.vexnum;w++)    //依次检查邻接矩阵v所在的行
            if((G.arcs[v][w]!=O}&&(!visited[w]}} DFS(G,w};
    //G.arcs[v][w]!=0表示w是v的邻接点, 如果w未访问, 则递归调用DFS
    }                                                 
    

算法6.6 采用邻接表表示图的深度优先搜索遍历

  • 算法描述:

    void DFS_AL (ALGraph G,int v)
    {//图G为邻接表类型, 从第v个顶点出发深度优先搜索遍历图G
       cout<<v;visited[v]=true; 
       p=G.vertices[v].firstarc; 
       while(p!=NULL)
       {
           w=p->adjvex; 
           if(!visited[w]) DFS(G,w); 
           p=p->nextarc;
       }
    }
    

3.深度优先搜索遍历的算法分析

分析上述算法,在遍历图时,对图中每个顶点至多调用一次 DFS 函数,因为一旦某个顶点被标志成巳被访问,就不再从它出发进行搜索。 因此,遍历图的过程实质上是对每个顶点查找其邻接点的过程,其耗费的时间则取决千所采用的存储结构。 当用邻接矩阵表示图时,查找每个顶点的邻接点的时间复杂度为 O(n2 ), 其中 n为图中顶点数。而当以邻接表做图的存储结构时,查找邻接点的时间复杂度为O(e), 其中e为图中边数。由此, 当以邻接表做存储结构时,深度优先搜索遍历图的时间复杂度为 O(n + e)。

6.5.2 广度优先搜索

1.广度优先搜索的过程:

(1) 从图中某个顶点v出发, 访问v。

(2) 依次访问v的各个未曾访问过的邻接点。

(3) 分别从这些邻接点出发依次访问它们的邻接点, 并使 “先被访问的顶点的邻接点“ 先千 ”后被访问的顶点的邻接点” 被访问。重复步骤(3), 直至图中所有已被访问的顶点的邻接点都被 访间到。
具体过程如下:

​ (1) 从顶点 v1 出发,访问 v1
​ (2) 依次访问 v1 的各个未曾访问过的邻接点 v2 和 v3
​ (3) 依次访问v2的邻接点v4和v5, 以及v3的邻接点v6和v7, 最后访问v4的邻接点vg。由千 这些顶点的邻接点均已被访问, 并且图中所有顶点都被访问, 由此完成了图的遍历。得到的顶点 访间序列为:
​ v1-v2-v3-v4-v5-v6-v7-v8

2.广度优先搜索遍历的算法实现

算法6.7 广度优先搜索遍历连通图

  • 算法描述:

    void BFS{Graph G,int v)
    {//按广度优先非递归遍历连通图G
        cout<<v;visited[v]=true;
        InitQueue(Q);
        EnQueue(Q,v);
        while(!QueueEmpty(Q))
        {
            DeQueue(Q,u);
            for(w=FirstAdjVex(G,u);w>= O;w=NextAdjVex(G,u,w)) 
                if(!visited[w])
                {
                    cout<<w; visited[w]=true; 
                    EnQueue(Q,v);
                }
        }
    }
    
    

3.广度优先搜索遍历的算法分析

分析上述算法,每个顶点至多进一次队列。遍历图的过程实质上是通过边找邻接点的过程,因此广度优先搜索遍历图的时间复杂度和深度优先搜索遍历相同,即当用邻接矩阵存储时,时间复杂度为O(n2 ); 用邻接表存储时,时间复杂度为O(n+ e)。两种遍历方法的不同之处仅仅在于对顶点访问的顺序不同。

图的应用

6.6.1 最小生成树

​ 假设要在 n 个城市之间建立通信联络网,则连通 n 个城市只需要 n-1 条线路。这时, 自然会考虑这样一个问题, 如何在最节省经费的前提下建立这个通信网。

​ 在每两个城市之间都可设置一条线路,相应地都要付出一定的经济代价。n个城市之间,最多可能设置 n(n- 1)/2 条线路,那么, 如何在这些可能的线路中选择 n-1,以使总的耗费最少呢?

​ 可以用连通网来表示n个城市, 以及n个城市间可能设置的通信线路, 其中网的顶点表示城市,边表示两城市之间的线路,赋予边的权值表示相应的代价。 对于 n 个顶点的连通网可以建立许多不同的生成树,每一棵生成树都可以是一个通信网。 最合理的通信网应该是代价之和最小的生成树。 在一个连通网的所有生成树中,各边的代价之和最小的那棵生成树称为该连通网的最小代价生成树 (Minimum Cost Spanning Tree), 简称为最小生成树

​ 构造最小生成树有多种算法,其中多数算法利用了最小生成树的下列一种简称为MST的性质:假设N= (V, E)是一个连通网,U 是顶点集 V 的一个非空子集。若(u, v)是一条具有最小权值(代价)的边,其中uEU, vEV-U, 则必存在一棵包含边(u, v)的最小生成树。

​ 可以用反证法来证明。假设网N的任何一棵最小生成树都不包含(u, v)。设T是连通网上的一棵最小生成树,当将边(u, v)加入到 T中时,由生成树的定义,T中必存在一条包含(u, v)的回路。另一方面,由于T是生成树,则在T上必存在另一条边(u',v'), 其中u'EU,v'EV-U,且 u 和 u'之间、 v 和 v'之间均有路径相通。 删去边(u', v'), 便可消除上述回路, 同时得到另一棵生 成树兀因为(u,v)的权值不高于(u',v'), 则 T的权值亦不高于 T, T是包含(u,v)的一棵最小生成 树。 由此和假设矛盾。

普里姆 (Prim) 算法克鲁斯卡尔 (Kruskal) 算法是两个利用 MST 性质构造最小生成树的 算法。 下面先介绍普里姆算法。

算法6.8 普里姆算法

  • 算法步骤

    • 首先将初始顶点u加入U中,对其余的每一个顶点vj, 将closedge[j]均初始化为到u的边信息。
    • 循环 n-I 次, 做如下处理:
      • 从各组边 closedge 中选出最小边 closedge[k], 输出此边;
      • 将K加入U中;
      • 更新剩余的每组最小边信息closedge[j], 对于 V-U 中的边,新增加了一条从K到j的边, 如果新边的权值比 closed ge[j].lowcost 小,则将 closedge[j].lowcost 更新为新边的权值。
  • 算法描述

    void MiniSpanTree_Prim(AMGraph G,VerTexType u)
    {//无向网G以邻接矩阵形式存储, 从顶点u出发构造G的最小生成树T, 输出T的各条边
        k=LocateVex(G,u);
        for(j=0;j<G.vexnum;++j)
            if(j!=k) closedge[j]={u,G.arcs[k][j]}; 
        closedge[k].lowcost=0; 
        for(i=l;i<G.vexnum;++i)
        {
            k=Min(closedge);
            v0=closedge[k].adjevx;
            v0=G.vexs[k]; 
            cout<<u0<<v0; 
            closedge[k].lowcost=0; 
            for(j=0;j<G.vexnum;++j)
                if(G.arcs[k][j]<closedge[j].lowcost)
         }
    }
    

算法6.9 克鲁斯卡尔算法

  • 算法步骤

    • 将数组Edge中的元素按权值从小到大排序。
    • 依次查看数组Edge中的边,循环执行以下操作:
      • 依次从排好序的数组Edge中选出一条边(U1,U2);
      • 在Vexset中分别查找VJ和Vz所在的连通分量VS1和VS2, 进行判断:
        • 如果VS1和VS2不等,表明所选的两个顶点分属不同的连通分量,输出此边,并合并 VS1和VS2两个连通分量;
        • 如果VS1和VS2相等,表明所选的两个顶点属于同一个连通分量,舍去此边而选择下 一条权值最小的边。
  • 算法描述

    void MiniSpanTree_ Kruskal(AMGraph G)
    {//无向网G以邻接矩阵形式存储,构造G的最小生成树T, 输出T的各条边
        Sort(Edge);                //将数组 Edge 中的元素按权值从小到大排序
        for(i=O;i<G.vexnum;++i)    //辅助数组,表示各顶点自成一个连通分址
            Vexset[i]=i; 
        for(i=O;i<G.arcnum;++i)    //依次查看数组 Edge 中的边
        {
            v1=LocateVex(G,Edge[i].Head); //v1为边的始点 Head 的下标
            v2=LocateVex(G,Edge[i].Tail); //v2为边的终点 Ta过的下标
            vs1=Vexset[v1];               //获取边 Edge[i]的始点所在的连通分址 vs1 
            vs2=Vexset[v2];               //获取边 Edge[i]的终点所在的连通分扯 vs2 
            if(vsl!=vs2)                  //边的两个顶点分属不同的连通分量
            {
                cout << Edge[i].Head << Edge[i].Tail;//输出此边
                for(j=O;j<G.vexnurn;++j)  //合并VS1和VS2两个分量, 即两个集合统一编号
                if(Vexset[j]==vs2) Vexset[j]=vsl; //集合编号为vs2的都改为vs1
            }                             //if                   
        }                                 //for
    }
    
    

标签:遍历,访问,算法,搜索,邻接,顶点
来源: https://www.cnblogs.com/Dear-bella/p/15878590.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有