1.图的存储结构

  数组表示法:用两个数组分别存储数据元素(顶点)的信息和数据元素之间的关系(边或弧)的信息。

 1 #define INFINITY  INT_MAX                                //最大值
 2 #define MAX_VERTEX_NUM    20                      //最大顶点数
 3 typedef enum{ DG,DN,UDG,UDN } GraphKind; //有向图,有向网,无向图,无向网
 4 typedef  struct ArcCell{
 5          VRType    adj;      //VRType是顶点关系类型。对无权图,用0或1表示相邻否;对有权图,则为权值类型    
 6          InfoType  *info;  //该弧相关信息的指针            
 7 }ArcCell, AdjMatrix[MAX_VERTEX_MAX][MAX_VERTEX_MAX];
 8 typedef  struct{
 9              VertexType  vexs[MAX_VERTEX_NUM];  //顶点向量
10              AdjMatrix   arcs;                                    //邻接矩阵
11             int          vexnum,arcnum;                  //图的当前顶点数和弧数
12             GraphKind   kind;                                   //图的种类标志    
13 }MGraph;

  以二维数组表示有n个顶点的图时,需存放n个顶点信息和n^2个弧信息的存储量。

  对于无向图,顶点vi的度是邻接矩阵中第i行的元素之和。

  对于有向图,第i行的元素之和为顶点vi的出度OD(vi),第j列的元素之和为顶点vj的入度ID(vj)。

 1 Status CreateGraph(MGraph &G){
 2      scanf(&G.kind);
 3      switch(G.kind){
 4             case  DG: return CreateDG(G); //构造有向图G
 5             case  DN: return CreateDN(G); //构造有向网G
 6             case  UDG: return CreateUDG(G); //构造无向图G
 7             case  UDN: return CreateUDN(G); //构造无向网G
 8     }      
 9 }    
10 
11 Status CreateUDN(MGraph &G){
12            scanf(&G.vexnum,&G.arcnum,&IncInfo); 
13            //构造顶点向量
14            //初始化邻接矩阵
15            //构造邻接矩阵
16               //输入一条边依附的顶点和权值
17               //确定v1和v2在G中的位置
18               //弧<v1,v2>的权值
19               //若弧含有相关信息,则输入
20               //置<v1,v2>对称弧<v2,v1>   
21 }

 

    链式表示法:邻接表,十字链表,邻接多重表

    邻接表:在邻接表中,对图中每个顶点建立一个单链表,单链表中的结点表示依附于该顶点的边,对有向图是当前顶点指向结点的弧。

      每个结点由3个域组成,邻接点域,链域,数据域。

      邻接点域:指示与当前顶点邻接的点在图中的位置。

      链域:指示单链表中的下一个结点。

      数据域:存储和边或弧相关的信息,如权值等。

      每个链表上附设一个头结点,指示单链表中的第一个结点,同时存储顶点的相关信息。

 1 #define MAX_VERTEX_NUM 20
 2 typedef struct ArcNode{
 3          int adjvex; //该弧指向的顶点的位置
 4         struct ArcNode *nextarc; //指向下一条弧的指针
 5         InfoType *info; //该弧相关信息的指针
 6 }ArcNode;
 7 
 8 typedef struct VNode{
 9         VertexType data; //顶点信息
10         ArcNode *firstarc; //指向第一条依附该顶点的弧的指针
11 }VNode,AdjList[MAX_VERTEX_NUM];
12 
13 typedef struct{
14         AdjList vertices; 
15         int vexnum,arcnum;  //图的当前顶点数和弧数
16         int kind;              //图的种类标志
17 }ALGraph;

     十字链表:

        十字链表是有向图的另一种链式存储结构。可以看成是将有向图的邻接表和逆邻接表结合起来得到的一种链表。

        在十字链表中,对应于有向图中每一条弧有一个结点,对应于每个顶点也有一个结点。结点的结构如下:

        弧结点: tailvex,headvex,hlink,tlink,info

        顶点结点:data,firstin,firstout

 1 #define MAX_VERTEX_NUM 20
 2 typedef struct ArcBox{
 3     int tailvex,headvex;
 4     struct ArcBox *hlink,*tlink;
 5     InfoType *info;
 6 }ArcBox;
 7 
 8 typedef struct VexNode{
 9     VertexType data;
10     ArcBox *firstin,*firstout;
11 }VexNode;
12 
13 typedef struct{
14     VexNode xlist[MAX_VERTEX_NUM];
15     int vexnum,arcnum;
16 }OLGraph;

    邻接多重表:邻接多重表是无向图的另一种链式存储结构。    

      邻接多重表的每一条边用一个结点表示,每一个顶点也用一个结点表示。

      边结点:mark,ivex,ilink,jvex,jlink,info

      顶点结点:data,firstedge

       

#define MAX_VERTEX_NUM 20
typedef enum{unvisited,visited}VisitIf;
typedef struct EBox{
  VisitIf mark;
  int ivex,jvex;
  struct EBox *ilink,*jlink;
  InfoType *info;        
}EBox;

typedef struct VexBox{
    VertexType data;
    EBox *firstedge;
}VexBox;

typedef struct{
    VexBox adjmulist[MAX_VERTEX_NUM];
    int vexnum,edgenum;
}AMLGraphy;

 

2.图的遍历

  从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次。

  通常有两条遍历图的路径:深度优先搜索和广度优先搜索。,它们对有向图和无向图都适用。

  深度优先搜索:

    深度优先搜索遍历类似于树的先根遍历,是树的先根遍历的推广。

    先访问顶点,然后访问一个邻接点,接着访问邻接点下一个邻接点,以此类推。

 1 Bool visited[MAX];
 2 Status (*VisitFunc)(int v);
 3 
 4 void DFSTraverse(Graph G,Status (*Visit)(int v)){
 5  VisitFunc=Visit;
 6  for(v=0;v<G.vexnum;++v) visited[v]=FALSE;
 7  for(v=0;v<G.vexnum;++v)
 8       if(!visited[v]) DFS(G,v);               
 9 }
10 
11 void DFS(Graph G,int v){
12        visited[v]=TRUE;
13        VisitFunc(v);
14        for(w=FirstAdjVex(G,v);w >= 0;w=NextAdjVex(G,v,w))
15             if(!visited[w]) 
16                 DFS(G,w);                 
17 }

                二维数组表示邻接矩阵作图的存储结构时,查找每个邻接点所需时间为O(n^2)

                当用邻接表作存储结构时,查找每个邻接点所需时间为O(e),遍历图的时间复杂度为O(n+e)

  广度优先搜索算法

    广度优先搜索遍历类似于树的按层次遍历的过程。

    先访问顶点,然后依次访问各个邻接点,以此类推。

    

 1 void BFSTraverse(Graph G,Status (*Visit)(int v)){
 2         for(v=0;v<G.vexnum;++v) visited[v]=FALSE;
 3         InitQueue(Q);
 4         for(v=0;v<G.vexnum;++v){
 5              if(!visited[v]){
 6                  visited[v]=TRUE;
 7                  Visit(v);
 8                  EnQueue(Q,v);
 9                  while(!QueueEmpty(Q)){
10                        DeQueue(Q,u);
11                       for(w=FirstAdjVex(G,u); w>=0; w=NextAdjVex(G,u,w)){
12                               if(!Visited[w]){
13                                      Visited[w]=TRUE;Visit(w); EnQueue(Q,w);
14                                }
15                         }
16                  }   
17              }
18         }   
19 }            

  深度优先搜索算法和广度优先搜索算法的时间复杂度相同,两者不同之处仅在于对顶点的访问顺序不同。  

 

3.图的连通性问题

  无向图的连通分量和生成树  

  有向图的强连通分量

  最小生成树:多数算法利用了最小生成树的下列一种简称为MST的性质:假设N=(V,{E})是一个连通网,U是顶点集V的一个非空子集。,

  若(u,v)是一条具有最小权值的边,其中u属于U,v属于V-U,则必存在一棵包含边(u,v)的最小生成树。

  利用普里姆算法求最小生成树:

      

struct{
   VertexType adjvex;
   VRType lowcost;
}closedge[MAX_VERTEX_NUM];

void MiniSpanTree_PRIM(MGraph G,VertexType u){
        //用普里姆算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边。
       //记录从顶点集U到V-U的代价最小的边的辅助数组定义:closedge[MAX_VERTEX_NUM]
        k=locate(G,u);
        for(j=0;j < G.vexnum;++j)
             if( j != k ) closedge[j]={u,G.arcs[k][j].adj}; //{adjvex,lowcost}
         closedge[k].lowcost=0;
         for(i=1;i < G.vexnum; ++i){
               k=minimum(closedge);
               printf(closedge[k].adjvex,G.vexs[k]);
               closedge[k].lowcost=0;
               for(j=0;j<G.vexnum;++j){
                    if(G.arcs[k][j].adj < closedge[j].lowcost)
                         closedge[j]={G.vexs[k],G.arcs[k][j].adj}
               }
         }   
}

  普里姆算法的时间复杂度为O(n^2),与网中的顶点相关,与网中的边数无关,因此适用于求边稠密的网的最小生成树。

  而克鲁斯卡尔算法则完全相反,它的时间复杂度为O(eloge)(e为网中的边数),因此适用于求边稀疏的网的最小生成树。

  克鲁斯卡尔算法过程:假设连通图N=(V,{E}),则令最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,{}),图中每个顶点自成一个连通

  分量。在E中选择权值最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入到T中,否则舍去此边而选择下一条最小权值的边,

  依次类推,直至T中所有顶点都在同一连通分量上为止。

 

4.关节点和重连通分量      

  关节点:假若在删去顶点v以及和v相关联的各边之后,将图的一个连通分量分割成两个或两个以上的连通分量,则称顶点v为该图的一个关节点。

       重连通图:一个没有关节点的连通图称为是重连通图。  

   若在连通图上至少删去k个顶点才能破坏图的连通性,则称此图的连通度为k。

   利用深度优先搜索便可求得图的关节点,并由此可判别图是否是重连通的。

 

 5.有向无环图及其应用

  一个无环的有向图称作有向无环图,简称DAG图。

  拓扑排序:由某个集合上的一个 偏序得到该集合上的一个全序,这个操作称之为拓扑排序。

  偏序:若集合X上的关系R是自反的,反对称的和传递的,则称R是集合X上的偏序关系。

  全序:设R是集合X上的偏序,如果对每个x,y属于X必有xRy或yRx,则称R是集合X上的全序关系。

  https://blog.csdn.net/liuchuo/article/details/51986226

  由偏序定义得到拓扑有序的操作称为拓扑排序。

  用顶点表示活动,用弧表示活动间的优先关系的有向图称为顶点表示活动的网,简称AOV-网。

  在网中,若从顶点i到顶点j有一条有向路径,则i是j的前驱,j是i的后继。若<i,j>是网中一条弧,则i是j的直接前驱,j是i的直接后继。

   在AVO网中不应该出现有向环。

  如何进行拓扑排序:

    1.在有向图中选一个没有前驱的顶点并输出之

    2.从图中删除该顶点和所有以它为尾的弧

    3.重复上述两步,直至全部顶点均已输出,否则说明当前有向图中存在环。

  拓扑排序的算法:

 1 Status TopologicalSort(ALGraph G){
 2            FindInDegree(G,indegree); //对各顶点求入度indegree[0..vernum-1]
 3            InitStack(S);
 4            for(i=0;i<G.vexnum;++i){
 5                 if(!indegree[i])Push(S,i); //入度为0顶点入栈
 6            }
 7            count=0;
 8            while(!StackEmpty(S)){
 9                 Pop(S,i);
10                 printf(i,G.vertices[i].data); //输出i号顶点并计数
11                 ++count;
12                 for(p=G.vertices[i].firstarc;p;p=p->nextarc){
13                       k=p->adjvex;
14                       if(!(--indegree[k]))
15                                Push(S,k);
16                 }
17            }
18            if(count < G.vexnum) return ERROR;
19            else 
20               return OK;
21 }

                对有n个顶点和e条弧的有向图,建立求各顶点的入度的时间复杂度为O(e),建零入度顶点栈的时间复杂度为O(n),总的时间复杂度为O(n+e)

  关键路径:

    与AOV-网相对应的是AOE-网即边表示活动的网。AOE-网是一个带权的有向无环图,其中顶点表示事件,弧表示活动,权表示活动持续的时间。通常AOE-网可用来估算工程  的时间。    

   从开始点到完成点的最长路径的长度(这里路径长度指路径上各活动持续时间之和),路径长度最长的路径叫做关键路径。

  最短路径:

    路径上第一个顶点为源点,最后一个顶点为终点。

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

    迪杰斯特拉算法:

      1.假设用带权的邻接矩阵arcs来表示带权有向图,arcs[i][j]表示弧<vi,vj>上的权值。

       若<vi,vj>不存在,则置arcs[i][j]为无穷大。S为已找到从v出发的最短路径的终点的集合,它的初始状态为空集。

       那么从v出发到图上各顶点(终点)vi可能达到的最短路径长度的初值为:

            D[i]=arcs[Locate Vex(G,v)][i]  vi属于V

      2.选择vj,使得

            D[j]=Min{D[i]  | vi属于V-S}

        vj就是当前求得的一条从v出发的最短路径的终点。令

                                           S=S U {j}

      3.修改从v出发到集合V-S上任一顶点vk可达的最短路径长度。如果:

          D[j]+arcs[j][k] < D[k] 则修改D[k]为 D[k]=D[j]+arcs[j][k]

      4.重复操作2,3共n-1次。即得到源点到终点最短路径序列。

    

void ShortesPath_DIJ(MGraph G,int v0,PathMatrix &P,ShortPathTable &D){
        //用Dijkstra算法求有向网G的v0顶点到其余顶点v的最短路径P[v]及其带权长度D[v]
        //若P[V][W]为TRUE,则w是从v0到v当前求得最短路径上的顶点。
       //final[v]为TRUE当且仅当v属于S,即已经求得从V0到V的最短路径
      for(v=0;v < G.vexnum;++v){
                 final[v]=FALSE;
                D[v]=G.arcs[v0][v];
                for(w=0;w < G.vexnum;++w) P[v][w]=FALSE;
                if(D[v] < INFINITY ) { //INFINITY =无穷大
                             P[v][v0]=TRUE;  P[v][v]=TRUE;
                   }     
      }
      D[v0]=0;final[v0]=TRUE;
       for(i=1;i<G.vexnum;++i){
             min=INFINITY;
             for(w=0;w<G.vexnum;++w)
                    if(!final[w])
                       if(D[w] < min){
                                   v=w;
                                   min=D[w];
                          }
              final[v]=TRUE;
              for(w=0;w<G.vexnum;++w)
                       if(!final[w] && (min + G.arcs[v][w]) < D[w] ){
                                D[w]=min+G.arcs[v][w];
                                P[w]=P[v];//P[w]=P[v]+[w]
                                P[w][w]=TRUE;
                       }
       }  
}

   该迪杰斯特拉算法复杂度为O(n^2)。

  每一对顶点之间的最短路径:

    有两种方法: 

      1.每次以一个顶点为源点,重复执行迪杰斯特拉算法n次。

      2.佛洛伊德算法:

      

void Shortest_FLOYD(MGraph G,PathMatrix &P[],DistancMatrix &D){
          //用Floyd算法求有向网G中各对顶点v和w之间的最短路径P[v][w]
         //及其带权长度D[v][w].若P[v][w][u]为TRUE,则u是从v到w当前求得
         //最短路径上的顶点
         for(v=0;v<G.vexnum;++v){
             for(w=0;w<G.vexnum;++w){
                      D[v][w]=G.arcs[v][w];
                      for(u=0;u<G.vexnum;++u){
                            P[v][w][u]=FALSE;   
                      }
                      if(D[v][w] < INFINITY) //从v到w有直接路径
                            P[v][w][v]=TRUE;
                            P[v][w][w]=TRUE; 
              }
         for(u=0;u<G.vexnum;++u){
              for(v=0;v<G.vexnum;++v){
                       for(w=0;w<G.vexnum;++w){
                                 if(D[v][u] + D[u][w] < D[v][w]){ //从v经u到w的一条路径更短
                                            D[v][w]=D[v][u]+D[v][w];
                                            for(i=0;i<G.vexnum;++i){
                                                     P[v][w][i]=P[v][u][i] || P[u][w][i];  
                                             }
                                  }
                       }
               }
          }  

         }         
}    

 

  

  

 

 

 

 

 

 

 

 

  

 

posted @ 2019-03-21 22:51  ciel-coding杂记  阅读(261)  评论(0编辑  收藏  举报