每周一道数据结构(一)图

图的定义


   图是由结点的有穷集合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中存在有向环,排序失败!");
}

 

posted @ 2013-05-19 14:39  cococo点点  阅读(2115)  评论(0编辑  收藏  举报