zoukankan      html  css  js  c++  java
  • 每周一道数据结构(一)图

    图的定义


       图是由结点的有穷集合V和边的集合E组成。其中,为了与树形结构加以区别,在图结构中常常将结点称为顶点,边是顶点的有序偶对,若两个顶点之间存在一条

    边,就表示这两个顶点具有相邻关系。

      图分为两类,一个是有向图,即每条边都有方向,另一个是无向图,即每条边都没有方向。
     
     
    相关问题

    • 图的遍历问题
    • 最小生成树问题
    • 单源最短路径问题
    • 拓扑排序问题
    • 关键路径

    图的遍历方法


      和树的遍历类似,图的遍历也是从某个顶点出发,沿着某条搜索路径对图中每个顶点各做一次且仅做一次访问。它是许多图的算法的基础。深度优先遍历和广度优先遍历是最为重要的两种遍历图的方法。它们对无向图和有向图均适用。

    1.深度优先算法的思想:

        假设图中任何一点都没有访问过,这时可以选择任意一点作为出发点,类似于数的前序遍历。则具体步骤如下:

      首先访问顶点V,并标记为以访问过。从顶点V的所有邻接点中,搜索为访问过的点W,如果存在W,则把W当作上一步的V,重复上一过程。直到图中所有可到达的路径都被访问过停止。如果图中还有为放过的顶点,则继续冲这部分定点中,选择一个节点作为顶点重复此过程。

    代码:

    void DFSTraverse(ALGraph *G)
    {
        for(int i=0;i<G->n;i++)
          visited[i]=FALSE; //标志向量初始化
        for(int i=0;i<G->n;i++)
          if(!visited[i]) //vi未访问过
            DFS(G,i); //以vi为源点开始DFS搜索
    }//DFSTraverse
    
    void DFS(ALGraph *G,int i)
    { 
        visit(G->adjlist[i].vertex);//访问顶点vi
        visited[i]=TRUE; //标记vi已访问
        p=G->adjlist[i].firstedge; //取vi边表的头指针
        while(p){//依次搜索vi的邻接点vj,这里j=p->adjvex
          if (!visited[p->adjvex])//若vi尚未被访问
            DFS(G,p->adjvex);//则以Vj为出发点向纵深搜索
          p=p->next; //找vi的下一邻接点
         }
    }//DFS

    2.广度优先算法的思想:

      设图G的初态是所有顶点均未访问过。在图中任选一顶点V,则广度优先遍历的具体步骤为:

      首先访问顶点V,接着依次访问v的所有邻接点W1,W2,…,Wt,然后再依次访问与Wl,W2,…,Wt邻接的所有未曾访问过的顶点。依此类推,直至图中所有和顶点V有路径相通的顶点都已访问到为止。此时从V开始的搜索过程结束。

    代码:

    void BFS(ALGraph*G,int k)
    {
        CreateQueue Q; //须将队列定义中DataType改为int
        EdgeNode *p;
        InitQueue(&Q);//队列初始化
         //访问源点vk
        visit(G->adjlist[k].vertex);
        visited[k]=TRUE; 
        EnQueue(&Q,k);//vk已访问,将其人队。(实际上是将其序号人队)
        while(!QueueEmpty(&Q)){//队非空则执行
            i=DeQueue(&Q); //相当于vi出队
            p=G->adjlist[i].firstedge; //取vi的边表头指针
            while(p){//依次搜索vi的邻接点vj(令p->adjvex=j)
                if(!visited[p->adivex]){ //若vj未访问过
                  visit(C->adjlistlp->adjvex].vertex); //访问vj
                  visited[p->adjvex]=TRUE; 
                  EnQueue(&Q,p->adjvex);//访问过的vj人队
                 }//endif
                p=p->next;//找vi的下一邻接点
             }//endwhile
          }//endwhile
       }//end of BFS

    生成树和最小生成树


      如果连通图G的一个子图是一棵包含G的所有顶点的树,则该子图称为G的生成树(SpanningTree)。生成树是连通图的包含图中的所有顶点的极小连通子图。图的生成树不惟一。从不同的顶点出发进行遍历,可以得到不同的生成树。

      对于连通的带权图(连通网)G,其生成树也是带权的。生成树T各边的权值总和称为该树的权,记作:
                                        这里:TE表示T的边集,w(u,v)表示边(u,v)的权。

         权最小的生成树称为G的最小生成树(Minimum SpannirngTree)。最小生成树可简记为MST。

    MST的性质:

      设G=(V,E)是一个连通网络,U是顶点集V的一个真子集。若(u,v)是G中所有的一个端点在U(u∈U)里、另一个端点不在U(即v∈V-U)里的边中,具有最小权值的一条边,则一定存在G的一棵最小生成树包括此边(u,v)。

    求MST的一般算法可描述为:

      针对图G,从空树T开始,往集合T中逐条选择并加入n-1条安全边(u,v),最终生成一棵含n-1条边的MST。

      当一条边(u,v)加入T时,必须保证T∪{(u,v)}仍是MST的子集,我们将这样的边称为T的安全边。

    伪代码:

     GenerieMST(G)
    {//求G的某棵MST
         T=NULL; //T初始为空,是指顶点集和边集均空
         while( T未形成G的生成树 ){
             找出T的一条安全边(u,v);//即T∪{(u,v)}仍为MST的子集
             T=T∪{(u,v)}; //加入安全边,扩充T
          }
         return T; //T为生成树且是G的一棵MST
       }

    Prim算法与Kruskal算法:

    Prim思想:

      首先定义一个空集合T,用来存放MST的边和顶点。从图中选取一个顶点u,找到与u相连接的一条最短的边(u,v)作为第一条边,把(u,v)边和顶点v加入到集合T中。接下来,我们要选择下一条最短边(轻边),要求这条边的起点在集合T中,终点在集合T外。再重复上面的过程,直到找到n-1条边为止。

      由于可能存在相同长度的轻边,这就导致了最小生成树不唯一。

     Prim伪码:

    PrimMST(G,T,r)
    {
       //求图G的以r为根的MST,结果放在T=(U,TE)中
        InitCandidateSet(…);//初始化:设置初始的轻边候选集,并置T=({r},¢)
        for(k=0;k<n-1;k++){ //求T的n-1条树边
            (u,v)=SelectLiShtEdge(…);//选取轻边(u,v);
             T←T∪{(u,v)};//扩充T,即(u,v)加入TE,点v并人集U
            ModifyCandidateSet(…); //根据点v调整候选轻边集
        } 
     }

      复杂度为O(n^2),与边数无关.

    Kruskal思想:

      与Prim算法不同,它只考虑每次所选的边为剩下的边集合中最短的那条边。这样就导致了,在找到最后一条边之前,集合T始终是一个森林。

    KruskalMST(G)
    {//求连通网G的一棵MST T=(V,¢); //初始化,T是只含n个顶点不包含边的森林 依权值的递增序对E(G)中的边排序,并设结果在E[0..e-1]中 for(i=0;i<e;i++) { //e为图中边总数 取E[0..e-1)中的第i条边(u,v) if u和v分别属于T中两棵不同的树then T=T∪{(u,v)};//(u,v)是安全边,将其加入T中 if T已是一棵生成树then `` return T; }//endfor return T; }

      复杂度0(elge),与边有关。

    最短路径


       单源最短路径问题:已知有向带权图(简称有向网)G=(V,E),找出从某个源点s∈V到V中其余各顶点的最短路径。

    1、边上权值相等的有向网的单源最短路径
         用求指定源点的BFS生成树的算法可解决。

    2、迪杰斯特拉(Dijkstra)算法求单源最短路径
         由Dijkstra提出的一种按路径长度递增序产生各顶点最短路径的算法。

    Dijkstra思想:

      设S为最短距离已确定的顶点集,V-S是最短距离尚未确定的顶点集。
      ①初始化
            初始化时,只有源点s的最短距离是已知的(SD(s)=0),故点集S={s},点集V-S为空。
      ②重复以下工作,按路径长度递增次序产生各顶点最短路径
           在当前点集V-S中选择一个最短距离最小的点来扩充点集S,以保证算法按路径长度递增的次序产生各顶点的最短路径。
           当点集V-S中仅剩下最短距离为∞的点,或者所有点已扩充到点集S时,s到所有顶点的最短路径就求出来了。
      注意:
         ①若从源点到某点的路径不存在,则可假设到该点的最短路径是一条长度为无穷大的虚拟路径。
         ②从源点s到终点v的最短路径简称为v的最短路径;s到v的最短路径长度简称为v的最短距离,并记为SD(v)。

     Dijkstra(G,D,s){
        //用Dijkstra算法求有向网G的源点s到各顶点的最短路径长度
        //以下是初始化操作
          S={s};D[s]=0//设置初始的点集S及最短距离D
          for( all i∈ V-S )do //对点集V-S中每个顶点i
              D[i]=G[s][i]; //设置i初始的估计距离为w<s,i>
           //以下是扩充红点集
          for(i=0;i<n-1;i++)do{//最多扩充n-1个点到点集S
               D[k]=min{D[i]:all i V-S}; //在当前点集V-S中选估计距离最小的顶点k
               if(D[k]等于∞)
                    return//点集V-S中所有点的估计距离均为∞时,
                         //表示这些顶点的最短路径不存在。
               S=S∪{k}; //将点扩充到点集S中
               for( all j∈V-S )do //调整V-S中剩余点的估计距离
                   if(D[j]>D[k]+G[k][j])
                    //新点k使原D[j]值变小时,用新路径的长度修改D[j],
                  //使j离s更近。
                       D[j]=D[k]+G[k][j];
              }
       }

    3、floyd算法

      通过一个图的权值矩阵求出它的每两点间的最短路径矩阵

    floyd思想: 

    1. 从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
    2. 对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比已知的路径更短。如果是更新它。

    伪码:

    void floyd(A,D)
    {
       //初始化
       D[u,v]=A[u,v]
    
       //更新
     For k:=1 to n
          For i:=1 to n
             For j:=1 to n
                If D[i,j]>D[i,k]+D[k,j] Then
                     D[I,j]:=D[I,k]+D[k,j];
    }

    拓扑排序


      对一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若<u,v> ∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(TopoiSicai Order)的序列,简称拓扑序列

      拓扑排序方法分为两种:无前趋的顶点优先的拓扑排序方法无后继的顶点优先拓扑排序方法

    1.无前趋的顶点优先的拓扑排序方法

       该方法的每一步总是输出当前无前趋(即人度为零)的顶点。

    伪码:

    NonPreFirstTopSort(G)
    {//优先输出无前趋的顶点
          while(G中有人度为0的顶点)do{
           从G中选择一个人度为0的顶点v且输出之;
           从G中删去v及其所有出边;
           }
          if(输出的顶点数目<|V(G)|)
            //若此条件不成立,则表示所有顶点均已输出,排序成功。
            Error("G中存在有向环,排序失败!");
     }

    2.无后继的顶点优先拓扑排序方法

        该方法的每一步均是输出当前无后继(即出度为0)的顶点。对于一个DAG,按此方法输出的序列是逆拓扑次序

    伪码:

    NonSuccFirstTopSort(G){//优先输出无后继的顶点
          while(G中有出度为0的顶点)do {
           从G中选一出度为0的顶点v且输出v;
           从G中删去v及v的所有人边
           }
          if(输出的顶点数目<|V(G)|)
               Error("G中存在有向环,排序失败!");
    }
  • 相关阅读:
    #Laravel笔记# 使用SMTP发送邮件功能
    #Laravel笔记# 监听事件
    idea 常见问题
    python常见函数汇总
    双DNN排序模型:在线知识蒸馏在爱奇艺推荐的实践
    机器学习算法GBDT
    Hive表数据同步到es
    目标检测资料
    zeppelin的介绍与使用
    Java汉字获取拼音、笔划、偏旁部首
  • 原文地址:https://www.cnblogs.com/coder2012/p/3079140.html
Copyright © 2011-2022 走看看