美文网首页算法
数据结构(图)

数据结构(图)

作者: yinxmm | 来源:发表于2018-10-04 13:52 被阅读0次

    1. 图的定义和基本术语

    • 线性结构中,元素仅有线性关系,每个元素只有一个直接前驱和直接后继;
    • 树形结构中,数据元素(结点)之间有着明显的层次关系,每层上的元素可能和下一层中多个元素相关,但只能和上一层中一个元素相关;
    • 图形结构中,数据元素(顶点)之间具有任意关系,图中任意两个数据元素之间都可能相关。

    (1) 图的定义

    图是由顶点的有穷非空集合和顶点之间边的集合组成, 通常表示为: G(V,E), 其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

    1. 无向边:若顶点Vi到Vj之间的边没有方向,则称这条边为无向边,用无序偶对(Vi,Vj) 来表示。如下左图,G= (V1,{E1}),其中顶点集合V1={A,B,C,D}; 边集合E1={ (A,B) ,(B,C),(C,D), (D,A) , (A,C) } 。圆括号
    2. 有向边:若从顶点Vi 到Vj的边有方向,则称这条边为有向边,也称为弧。用有序偶〈Vi,Vj>来表示, Vi称为弧尾, Vj称为弧头。 如果图中任意两个顶点之间的边都是有向边,则称该图为有向图。连接顶点A到D的有向边就是弧,A是弧尾,D是弧头,<A, D>表示弧,注意不能写成<D, A>。如下右图,G= (V2,{E2}),其中顶点集合V2={A,B,C,D}; 弧集合E2={<A,D>,<B,A>,<C,A>,<B,C>}。尖括号

    (2) 图的基本术语

    1. 子图: 假设有两个图G= (V,{E})和G'= (V',{E'}),如果V'是V的子集,且E'是E的子集,则称G'为G的子图。如下图带底纹的图均为左侧无向图与有向图的子图。
    2. 无向完全图和有向完全能图:在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。含有n个顶点的无向完全图有n(n-1)/2条边。在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图。含有n个顶点的有向完全图有n (n-1) 条边。
    3. 稀疏图和稠密图:有很少条边或弧的图称为稀疏图,反之称为稠密图,这里的概念是相对而言的。
    4. 权和网:有些图的边或弧具有与它相关的数字,这种与图的边或弧相关的数叫做权。这些权可以表示从一个顶点到另一个顶点的距离或耗费。这种带权的图通常称为网。如下图就是一张带权的图,即标识中国四大城市的直线距离的网,此图中的权就是两地的距离。
    5. 邻接点:对于无向图G= (V,{E}), 如果边(v,v')属于E, 则称顶点v和v‘互为邻接点,即v和v'相邻接、边(v,v')依附于顶点v和v',或者说(v,v')与顶点v和v'相关联。
    6. 度、入度和出度:点v的度是和v相关联的边的数目,记为TD(v)。如上图左侧上方的无向图,顶点A与B互为邻接点,边(A,B) 依附于顶点A 与B 上,顶点A 的度为3。而此图的边数是5,各个顶点度的和=3+2+3+2=10,推敲后发现,边数其实就是各顶点度数和的一半,多出的一半是因为重复两次计数。
      对于有向图G= (V,{E}),如果弧<v,v'>属于E,则称顶点v邻接到顶点v',顶点v'邻接自顶点v的弧<v,v'>和顶点v, v'相关联。以顶点v为头的弧的数自称为v的入度
      ,记为ID (v); 以v为尾的弧的数目称为v的出度,记为OD (v); 顶点v的度为TD(v) =ID(v) +OD (v)。上图 左侧下方的有向图,顶点A的入度是2 (从B到A的弧,从C到A的弧),出度是1 (从A到D的弧),所以顶点A 的度为2+1=3。此有向图的弧有4 条,而各顶点的出度和=1+2+1+0=4,各顶点的入度和=2+0+1+1=4。
    7. 路径和路径的长度:从顶点v 到顶点v'的路径是一个顶点序列。路径的长度是路径上的边或弧的数目。有向图的路径也是有向的。
      8.回路或环:第一个顶点到最后一个顶点相同的路径称为回路或环。
      9.简单路径、简单回路或简单环:序列中顶点不重复出现的路径称为简单路径。除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路,称为简单回路或简单环。如下图,左侧的环因第一个顶点和最后一个顶点都是B,且C、 D、 A没有重复出现,因此是一个简单环。 而右侧的环,由于顶点C的重复,它就不是简单环。



      10.连通、连通图和连通分量:在无向图G中,如果从顶点v到顶点v'有路径,则称v和v'是连通的。 如果对于图中任意两个顶点vi、vj ∈E, vi,和vj都是连通的,则称G是连通图。 下图的图1,它的顶点A到顶点B、 C、 D都是连通的,但显然顶点A与顶点E或F就无路径,因此不能算是连通图。而图2,顶点A、 B、 C、D相互都是连通的,所以它本身是连通图。



      无向图中的极大连通子图称为连通分量。注意连通分量的概念,它强调:
    • 要是子图;
    • 子图要是连通的;
    • 连通子图含有极大顶点数;
    • 具有极大顶点数的连通子图包含依附于这些顶点的所有边。
      上图中,图1是一个无向非连通图。 但是有两个连通分量,即图2和图3。而图4,尽管是图1的子图,但是它却不满足连通子图的极大顶点数(图2满足)。 因此它不是图1的无向图的连通分量。
    1. 强连通图和强分量:在有向图G中,如果对于每一对vi,vj属于E,vi不等于vj,从vi到vj和vj 到vi都有路径,则称G是强连通图。有向图中的极大强连通子图称作有向图的强连通分量。
    2. 连通图的生成树:一个极小的连通子图, 它含有图中全部的n 个顶点,但只有足以构成一棵树的n-1条边。比如下图的图1是一普通图,但显然它不是生成树,当去掉两条构成环的边后,比如图2 或图3,就满足n个顶点n-1条边且连通的定义了, 它们都是一棵生成树。从这里也可知道,如果一个图有n 个顶点和小于n-1条边,则是非连通图,如果多于n-1 边条,必定构成一个环, 因为这条边使得它依附的那两个顶点之间有了第二条路径。比如图2 和图3,随便加哪两顶点的边都将构成环。 不过有n-1条边并不一定是生成树,比如图4。


    3. 有向树和生成森林:如果一个有向图恰有一个顶点的入度为0,其余顶点的入度均为1,则是一棵有向树。对有向树的理解比较容易,所谓入度为0其实就相当于树中的根结点, 其余顶点入度为1就是说树的非根结点的双亲只有一个。一个有向图的生成森林由若干棵有向树组成, 含有图中全部顶点,但只有足以构成若干棵不相交的有向树的弧。 如下图的图1 是一棵有向图。去掉一些弧后,它可以分解为两棵有向树,如图2和图3,这两棵就是图1有向图的生成森林。


    2.图的存储结构

    (1)邻接矩阵

    图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。设图G有n个顶点,则邻接矩阵是一个nxn的方阵,定义为:



    如下无向图,



    如下有向图

    我们知道,每条边上带有权的图叫做网,如果要将这些权值保存下来,可以采用权值代替矩阵中的0、1,权值不存在的元素之间用∞表示,如下图,左图是一个有向网图,右图就是它的邻接矩阵。


    邻接矩阵结构:

    #define OK 1
    #define ERROR 0
    #define TRUE 1
    #define FALSE 0
    #define MVNum 100 /* 最大顶点数,应由用户定义 */
    #define MaxInt 65535  //表示极大值,即∞
    typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
    typedef char VerTexType; /* 顶点类型应由用户定义  */
    typedef int ArcType; /* 边上的权值类型应由用户定义 */
    typedef struct{ 
        VertexType vexs[MVNum]; /* 顶点表 */   
        EdgeType arc[MAXVEX][MAXVEX];/* 邻接矩阵,可看作边表 */   
        int vexnum, arcnum; /* 图中当前的顶点数和边数  */
    }AMGraph;
    
    

    1. 采用邻接矩阵表示法创建无向网

    (1) 输入总顶点数和总边数。
    (2) 依次输入点的信息存入顶点表中。
    (3) 初始化邻接矩阵,使每个权值初始化为极大值。
    (4)构造邻接矩阵。依次输入每条边依附的顶点和其权值。确定两个顶点在图中的位置之后,使相应的边赋予相应的权值,同时使其堆成边赋予相同的权值。
    该算法的时间复杂度为O(n*n)

    Status CreateUDN(AMGraph &G)
    {//采用邻接矩阵表示法,创建无向网G
        cin>>G.vexnum>>G.arcnum; //输入总顶点数和总边
        for(i=0;i<G.vexnum;++i)
        {
            cin>>G.vexs[i];//依次输入点的信息
        }
        for(=0;i<G.vexnum;++i)
            for(j=0;j<G.vexnum;j++)
                G.arcs[i][j] = MaxInt;//初始化邻接矩阵,边的权值均置为最大值MaxInt
        for(k=0;k<G.racnum;++k) //构造邻接矩阵
        {
              cin>>v1>>v2>>w;//输入一条边依附的顶点及权值
              i=LocateVex(G,v1);j=LocateVex(G,v2);que'd
              G.arcs[i][j] = w;
              G.arcs[j][i] = w;
        }
        return OK;
    }
    
    int LocateVex(AMGraph G,VerTextType u)
    {
          int i;
          for(i=0;i<G.vexnum;i++)
          {
                  if( u == G.vexs[i]) return i;
           }
          return -1;
    }
    
    2.邻接矩阵表示法的优缺点

    优点:
    (1) 便于判断两个顶点之间是否有 边,即根据A[i][j] = 0或1来判断。
    (2) 便于 计算各顶点的度。对于无向图,邻接矩阵的第i行元素之和就是顶点i的度。对于有向图,第i行元素之和就是顶点i的出度,第i列元素之和就是顶点i的入度。
    缺点:
    (1) 不便于增加删除顶点。
    (2) 空间复杂度高。如果是有向图,n个顶点需要nn个单元存储边。如果无向图,其邻接矩阵是对称的,所以对规模较大的邻接矩阵可以采用压缩存储的方法,仅存储下三角元素,这样需要n(n-1)/2个单元。无论哪种存储方式,邻接矩阵表示法的空间复杂度均为0(nn)

    (2)邻接表

    数组与链表相结合的存储方法称为邻接表。
    1.图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过数组可以较容易地读取顶点信息,更加方便。另外,对于顶点数组中,每个数据元素还需要存储指向第一个邻接点的指针,以便于查找该顶点的边信息。

    1. 图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储,无向图称为顶点vi 的边表,有向图则称为顶点vi作为弧尾的出边表。
      如图是一个无向图的连接表结构,有向图则类似。



      对于带权值的网图,可以在边表结点定义中再增加一个weight 的数据域,存储权值信息即可,如下图所示。


    图的邻接表存储表示

    #define MVNum 100 //最大顶点数
    typedef char VertexType; /* 顶点类型应由用户定义 */
    typedef int OtherType; /* 边上的权值类型应由用户定义 */ 
    typedef struct ArcNode /* 边表结点  */
    {   
          int adjvex;    /* 邻接点域,存储该顶点对应的下标 */  
          OtherType info;       /* 用于存储权值,对于非网图可以不需要 */ 
          struct ArcNode *next; /* 链域,指向下一个邻接点 */
    }ArcNode; 
    typedef struct VNode /* 顶点表结点 */
    {   
          VertexType data; /* 顶点域,存储顶点信息 */ 
          ArcNode *firstedge;/* 指向第一条依附顶点的边指针 */
    }VNode, AdjList[MVNum];
    typedef struct{ 
          AdjList vertices;     
          int vexnum,arcnum; /* 图中当前顶点数和边数 */
    }ALGraph; 
    

    1.采用邻接表表示法创建无向图

    (1)输入总顶点数和总边数
    (2)依次输入点的信息存入顶点表中,使每个表头结点的指针域初始化为NULL。
    (3) 创建邻接表。依次输入每条边依附的两个顶点,确定这两个顶点的序号i和j之后,将此边结点分别插入vi 和vj对应的两个链表的头部。
    该算法的时间复杂度为O(n+e)

    Status CreateUDG(ALGraph &G)
    {
        cin>>G.vexnum>>G.racnum;//输入总顶点数,总边数
        for(i=0;i<G.vexnum;i++)//输入各点,构造表头结点表
        {
              cin>>G.vertices[i].data;//输入顶点值
              G.vertices[i].firstarc = NULL;//初始化表头结点的指针域为NULL
         }
        for(k=0k<G.arcnum;k++)//输入各边,构造邻接表
        {
              cin>>v1>>v2;
              i = LocateVex(G,v1);j=LocateVex(G,v2);//确定v1和v2在G中位置,即顶点G.vertices中的序号
              p1 = new ArcNode;//生成一个新的边结点*p1
              p1->adjvex = j;//邻接点序号为j
              p1->nextarc = G.vertices[i].firstarc;G.vertices[i].firstarc=p1;//将新结点*p1插入顶点vi的边表头部
              p2 = new ArcNode;
              p2->adjvex = i;
              p2->nextarc = G.vertices[j].firstarc;G.vertices[j].firstarc=p2;
         }
          return OK;
    }
    

    2. 邻接表表示法的优缺点

    优点:
    (1) 便于增加和删除结点。
    (2) 便于统计边的数目,按顶点表顺序扫描所有边表可得到边的数目,时间复杂度为O(n+e)。
    (3)空间效率高。对于一个具有n个顶点e条边的图G,若图G是无向图,则在邻接表表示中有n个顶点表结点和2n个边表结点。若G是有向图,则在它的邻接表表示或逆邻接表表示中均有n个顶点表结点和e个边表结点。因此,邻接表的空间复杂度为O(n+e)。
    缺点:
    (1) 不便于判断顶点之间是否有边,要判断vi 和vj之间是否有边,就需扫描第i个边表,最换情况下要耗费O(n)时间。
    (2) 不便于计算有向图各个顶点的度。

    3. 图的遍历

    从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历。

    (1) 深度优先遍历

    深度优先遍历类似于数的先序遍历,是树的先序遍历的推广。

    1. 从图中某个顶点v出发,访问v。
    2. 找到刚访问过得顶点的第一个未被访问的邻接点,访问该顶点。以该顶点为新顶点,重复此步骤,直至刚访问的顶点没有未被访问的邻接点为止。
    3. 返回前一个访问过得且扔有未被访问的邻接点的顶点,找到该顶点的下一个未被访问的邻接点,访问该顶点。
    4. 重复步骤2,3,直至图中所有顶点都被访问过。


    1. 深度优先遍历算法的实现

    为了在遍历过程中便于区分顶点是否已经被访问,需附设访问标志组visited[i]n],其初值为false,一旦某个顶点被访问,则其相应的分量置为true。

    深度优先遍历连通图

    1. 从图中某个顶点v出发,访问v,并置visited[v]的值为true。
    2. 依次检查v 的所有邻接点w,如果visited[w]的值为false,再从w出发进行递归遍历,直至图中所有顶点都被访问过。
    bool visited[MVNum];//访问标志数组,其初值为false
    void DFS(Graph G,int v)
    {//从v个顶点出发递归地深度优先遍历图
          cout<<v;visited[v]=true;//访问第v个顶点,并置访问标志数组相应分量值为true
        for(w=FirstAdjVex(G,v);w>0;w=NextAdjVex(G,v,w))
    //依次检查v的所有邻接点w,FirstAdjVex(G,v)表示v第一个邻接点
    //NextAdjVex(G,v,w)表示v相对于w的下一个邻接点,w>0表示有邻接点。
              if(!visited[w]) DFS(G,w);//对于v的尚未访问的邻接顶点w递归调用DFS
    }
    

    深度优先遍历非连通图

    void DFSTraverse(Graph G)
    {
          for(v=0;v<G.vexnum;v++) visited[v] = false; //访问标志数组初始化
         for(v=0;v<G.vexnum;v++)  if(!visited[v]) DFS(G,v);//对尚未访问的顶点调用DFS
    }
    

    采用邻接矩阵表示图的深度优先遍历

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

    采用邻接表表示图的深度优先遍历

    void DFS_AL(ALGraph G  ,int v)
    {//图G为邻接表类型,从v个顶点出发的深度优先遍历图
        cout <<v;visited[v]=true;
        p= G.vertices[v].firstarc;//p指向v的边链表的第一个结点
        while(P!=NULL)//边结点非空
       {
            w = p->adjvex;表示w是v 的邻接点
            if(!visited[w]) DFS_AL(G,w);//如果w未被访问,则递归调用DFS_AL 
            p=p->nexttrac;//p指向下一个边结点
        }
    }
    

    (2)广度优先遍历

    图的广度优先遍历就类似于树的层序遍历。

    1. 从图中某个顶点v出发,访问v。
    2. 依次访问v的各个未被访问过得邻接点。
    3. 分别从这些邻接点出发依次访问他们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问。重复步骤3,直至图中所有已被访问的顶点的邻接点都被访问到。


    1.广度优先遍历连通图

    1. 从图中某个顶点v出发,访问v,并置visited[v]的值为true,然后将v进队。
    2. 只要队列不为空,则重复下述操作:
    • 队头顶点u出队。
    • 依次检查u的所有邻接点w,如果visited[w]的值为false,则访问w,并置visited[w]的值为true。然后将w进队。
    void BFS(Graph G,int v)
    {
        cout<<v;visited[v] =true; //访问第v个顶点,并置访问标志数组相应分量值为true
        InitQueue(Q);//辅助队列Q初始化,置空
        EnQueue(Q,v);//v进队
        while(!QueueEmpty(Q))//队列非空
        {
              DeQueue(Q,u);
              for(w=FirstAdjVex(G,u);w>=0;w=NextAdjVex(G,u,w))
              {//依次检查u的所有邻接点w,FirstAdjVex(G,u)表示u的第一个邻接点
    //NextAdjVex(G,u,w)表示u相对于w的下一个邻接点,w>=0表示存在邻接点
                      if(!visited[w])//w为u的尚未访问的邻接顶点
                      {
                              cout<<w;visited[w]=true;//访问w
                              EnQueue(Q,w);//w进队
                      }
                }
        }
    }
    
    

    4. 图的应用

    (1) 最小生成树

    如下图是个带权值的网结构图。要用最小的成本将所有元素连接起来,即n个顶点,用n-1条边把连通图连接起来,并且使得权值的和最小。定义:把构造连通网的最小代价生成树称为最小生成树。这里介绍两种经典算法。


    1. 普利姆(Prim)算法

    假设N=(V,E)是连通图,TE是N上的最小生成树中边的集合。

    1. U={u0}(u0∈V), TE={ }。
    2. 在所有u∈U,v∈V-U的边(u,v)∈E 中找一条代价(权值)最小的边(u0,v0) 并入集合TE,同时v0并入U。
    3. 重复2,直至U=V为止。
      此时TE中必有n-1条边,则T= (V,{TE}) 为N的最小生成树。


    算法步骤:
    为实现这个算法需附设一个辅助数组closedge,以记录从U到V-U具有最小权值的边。对每个顶点vi∈V-U,在辅助数组中存在一个相应分量closedge[i-1],他包含2个域:lowcost和adjvex,其中lowcost存储最小边上的权值,adjvex存储最小边在U中的那个顶点。显然closedge[i-1].lowcost = Min{cost(u,vi)|u∈U},其中cost(u,v)表示赋予边(u,v)的权。

    struct
    {
          VerTextType adjvex;//最小边在U中的那个顶点
          ArcType lowcost;//最小边上的权值
    }closedge[MVNum];
    
    1. 首先将初始顶点u加入U中,对于其余的每一个顶点vj,将closedeg[j]均初始化为到u的边信息。
    2. 循环n-1次,做如下处理:
    • 从各组边closedeg中选出最小边closedge[k],输出此边。
    • 将k加入U中。
    • 更新剩余的每组最小边信息closedeg[j],对于V-U中的边,新增加一条从k到j的边,如果新边的权值比closedeg[j].lowcost小,则将closedge[j].lowcost更新为新边的权值。
    void MiniSpanTree_Prim(AMGraph G,VerTexType u)
    {//无向网G以邻接矩阵形式存储,从顶点u出发构造G的最小生成树,输出T的各条边
        k = LocateVex(G,u);//k为顶点u的下标
        for(j=0 ;j<G.vexnum;++j)//对于V-U的每一个顶点vj,初始化closedge[j]
              if (j!=k) closedge[j] = {u,G.arcs[k][j]};//{adjvex,lowcost}
        closedeg[k].lowcost = 0;//初始,U={u}
        for(i=1;i<G.vexnum;++i)
        {//选择其余n-1个顶点,生成n-1条边(n = G.vexnum)
              k= Min(colsedge);
    //求出T的下一个结点,第k个顶点,closedge[k]中存有当前最小边
              u0 = closedge[k].adjvex;//u0为最小边的一个顶点,u0 ∈U 
              v0 = G.vexs[k];//v0为最小边的另一个顶点,v0∈V-U
              cout<<u0<<v0;//输出当前最小边(u0,v0)
              closedge[k].lowcost = 0;//第k个顶点并入U集
              for(j=0;j<G.vexnum;++i)
                    if(G.arcs[k][j] < closedge[j].lowcost)//新顶点并入U后重新选择最小边
                            colsedge[j] = {G.vexs[k],G.arcs[k][j]};
        }    
    }
    

    2. 克鲁斯卡算法

    假设连通网N=(V,E),将N中的边按权值从小到大的顺序排列。

    1. 初始状态为只有n个顶点而无边的非连通图T={V,{ }},图中每个顶点自成一个连通分量。
    2. 在E 中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入到T中(即不形成回路),否则舍去此边而选择下一条代价最小的边。
    3. 重复2,直至T中所有顶点都在同一个连通分量上为止。



      算法的实现要引入以下辅助的数据结构。

    1. 结构体数组Edge:存储边的信息,包括边的两个顶点信息和权值。
    struct
    {
          VerTexType Head;//边的始点
          VerTexType Tail;//边的终点
           ArcType  lowcost;  //    边上的权值
    }Edge[arcnum]
    
    1. Vexset[i]:标识各个顶点所属的连通分量。对每个顶点vi∈V ,在辅助数组存在一个相应元素Vexset[i]表示该顶点所在连通分量。初始时Vexset[i],表示各个顶点自成一个连通分量。
    int Vexset[Mvnum];
    

    算法步骤:

    1. 将数组Edge中的元素按权值从小到大排序。
    2. 依次查看数组Edge中的边,循环执行以下操作:
    • 依次从排序好的数组Edge中选出一条边(U1,U2)
    • 在Vexset中分别查找v1和v2 所在的连通分量vs1 和vs2,并进行判断:

    *如果vs1 和vs2不等,表明所选的两个顶点分属不同的连通分量,输出此边,并合并vs1 和vs2两个连通分量。
    *如果vs1 和vs2相等,表明所选的两个顶点属于同一个连通分量,舍去此边而选择下一条权值最小的边。

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

    (2) 最短路径

    对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点是源点,最后一个顶点是终点。关于最短路径主要有两种算法,迪杰斯特拉(Dijkstra) 算法和弗洛伊德(Floyd) 算法。

    1. 迪杰斯特拉算法

    从某个源点到其余各顶点的最短路径

    对于网N=(V,E),将N中的顶点分成两组:
    第一组S:已求出的最短路径的终点集合(初始时只包含源点v0)。
    第二组V-S:尚未求出最短路径的终点集合(初始时V-{v0})。
    算法将各项顶点与v0 间最短路径长度递增的次序,逐个将集合V-S的顶点加入集合S中去。在这个过程中,总保持从v0到集合S中各顶点的路径长度始终不大于到集合V-S中各顶点x 的路径。



    算法的实现要引入以下辅助数据结构

    1. 一位数组S[i]:记录从源点v0到终点vi是否已被确定最短路径长度,true表示确定,false表示尚未确定。
    2. 一位数组Path[i]:记录从源点v0到终点vi的当前最短路径上vi的直接前驱顶点序号。其初始值为:如果从v0到vi有弧,则Path[i]为v0,否则为-1。
    3. 一位数组D[i]:记录从源点v0到终点vi的当前最短路径长度。其初始值为:如果从v0到vi有弧,则D[i]为弧上的权值,否则为∞。
      显然,长度最短的一条最短路径必为(v0,vk),满足以下条件:
      D[k] = Min{D[i]|vi∈V-S}
      求得顶点vk的最短路径后,将其加入到第一组顶点集S中。
      每当加入一个新的顶点到顶点集S,对第二组剩余的各个顶点而言,多一个中转顶点,从而多一个中转路径,所以要对第二组剩余的各个顶点的最短路径长度进行更新。
      原来v0到vi的最短路径长度为D[i],加入k作为中间顶点的中转路径长度为:D[k]+Garcs[k][i],若D[k]+Garcs[k][i]<D[i],则用D[k]+Garcs[k][i]取代D[i]。
      更新后,再选择数组D中值最小的顶点加入到第一组顶点集S中,如此进行下去,直至图中所有顶点到第一组顶点集S中为止。




    2. 弗洛伊德算法

    每个顶点之间的最短路径


    (3) 拓扑排序

    在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,我们称为AOV网。AOV网中的弧表示活动之间存在的某种制约关系,且不能存在回路。 设G=(V,E)是一个具有n个顶点的有向图, V中的顶点序列v1,v2,……, vn, 满足若从顶点vi到vj有一条路径,则在顶点序列中顶点vi必在顶点vj之前。 则称这样的顶点序列为一个拓扑序列。所谓拓扑排序,其实就是对一个有向图构造拓扑序列的过程。构造时会有两个结果,如果此网的全部顶点都被输出,则说明它是不存在环(回路)的AOV网;如果输出顶点数少了,哪怕是少了一个,也说明这个网存在环(回路),不是AOV网。
    拓扑排序的基本思路是: 从AOV网中选择一个入度为0 的顶点输出,然后删去此顶点,并删除以此顶点为尾的弧,继续重复此步骤,直到输出全部顶点或者AOV网中不存在入度为0的顶点为止。整个算法的时间复杂度为O(n+e)。

    https://blog.csdn.net/qq_35644234/article/details/60578189

    (4) 关键路径

    https://blog.csdn.net/qq_35644234/article/details/52664108

    相关文章

      网友评论

        本文标题:数据结构(图)

        本文链接:https://www.haomeiwen.com/subject/dqexaftx.html