图的知识归纳

/*
* 线性结构: 一对一
 树性结构: 一对多
 图结构:   多对多
* 【部分算法参考于:http://kjwy.5any.com/sjjg/index1.htm
*
*  【图】
* 【一】图的定义:【有一堆基本术语和五种存储结构:http://kjwy.5any.com/sjjg/index1.htm
   

 

 【二】图的遍历:
   
    ①深度优先搜索(DFS): 【类似于树的先根遍历:遍历后生成的树叫“深度优先生成树”】
    从指定的V0点开始访问,访问邻节点,再访问该邻节点的其他邻接点,直到所有的邻接点都访问完毕
    void DFSTraverse(Graph G, Status(*visit)(int v))
    {
   VisitFunc = Visit;
   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(Graph G, int v)
    {
   visited[v] = TRUE;
   VisitFunc(v);
   for (w=FirstAdjVex(G, v); w!=0; w=NextAdjVex(G, v, w))
    if (!visited[w]) //对v的尚未访问的邻接点w递归调用DFS
     DFS(G, w);
    }
   

    ②广度优先搜索(BFS):【搜索生成的树叫做“广度优先生成树”】
    从图中的某个顶点V0开始,在访问此顶点之后依次访问V0的所有未访问的邻接点,之后按这些邻接点被访问
    的先后次序【这里要用队列,存下先后顺序】依次访问它们的邻接点, 直至图中所有和V0有路径想通的顶点
    都被访问到,若此时途中尚有顶点未访问到,则另选图中一个未曾被访问的顶点做起始点,重复上述操作,直到
    图中所有顶点都被访问到为止!

    void BFSTraverse(Graph G, status (*Visit)(int v))
    {
   for (v=0; v<G.vexnum; ++v)
   {
    visited[v] = FALSE;
   }
   InitQueue(Q); //置空的辅助队列Q
   for (v=0; v<G.vexnum; ++v)
   {
    if (!visited[v]) //如果未被访问
    {
     EnQueue(Q, v);
     visited[v] = TRUE;
     Visit(v);
     while (!QueueEmpty(Q))
     {
      DeQueue(Q, u); //队头元素出队并置为u
      for (w=FirstAdjVex(G, u); w!=0; w=NextAdjVex(G, u, w))
      {
       if (!visited[w])
       {
        EnQueue(Q, w);
        visited[w] = TRUE;
        Visit(w);
       }
      }
     }
    }
   }
    }

    ③图遍历的应用:
    Ⅰ 求一条从顶点i到顶点s的简单路径【在深度优先搜索基础上得出】
   
    Ⅱ 求两个顶点之间的路径长度最短的路径【在广度优先搜索基础上做比较简单】

 【三】图的算法
  ①最小生成树
    问:假设要在n个城市之间建立通讯网络,则联通n个城市只需要修n-1条线路
    如何在最节省经费的前提下建立这个通讯网?
    【分析:构造网的一颗最小生成树,即:在e条带权边中选取n-1条(不构成回路),使“权值之和”最小】
   
    算法一:【普利姆算法(prim算法)】 O(n^2),适用于稠密图
   一、普里姆算法的基本思想:
   从连通网N={V,E}中的某一顶点U0出发,选择与它关联的具有最小权值的边(U0,v),将其顶点加入到生成树
   的顶点集合U中。以后每一步从一个顶点在U中,而另一个顶点不在U中的各条边中选择权值最小的边(u,v),
   把它的顶点加入到集合U中。如此继续下去,直到网中的所有顶点都加入到生成树顶点集合U中为止。
   
   二、 Prim算法
   void minispantree_prim(mgraph G,vertextype u){

     //用普里姆算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边。

     //记录从顶点集U到V-U的代价最小的边的辅助数组定义:

     //struct{

     //   Vertextype adjvex;

     //   VRType lowcost;

     //  }closedge[MAX_VERTEX_NUM];

       k=locatevex(G,u);

       for(j=0;j<G.vexnum;++j)  //辅助数组初始化

         if(j!=k)closedge[j]={u,G.arcs[k][j].adj};

       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};

       }

   }


   
   
    算法二:【克鲁斯卡尔算法(Kruskal算法)】: 需对e条边按权值进行排序,O(eloge),则适用于稀疏图
   一、克鲁斯卡尔算法的基本思想:
   设有一个有n个顶点的连通网N={V,E},最初先构造一个只有n个顶点,没有边的非连通图T={V, E},
   图中每个顶点自成一个连通分量。当在E中选到一条具有最小权值的边时,若该边的两个顶点落在
   不同的连通分量上,则将此边加入到T中;否则将此边舍去,重新选择一条权值最小的边。
   如此重复下去,直到所有顶点在同一个连通分量上为止。

   二、Kruskal算法
   void kruskal (edgeset ge, int n, int e)

   // ge为权按从小到大排序的边集数组

   {        int set[MAXE], v1, v2, i, j;

      for (i=1;i<=n;i++)

      set[i]=0;        // 给set中每个元素赋初值

      i=1;          // i表示获取的生成树中的边数,初值为1

      j=1;          // j表示ge中的下标,初始值为1

      while (j<n && i<=e)

       // 检查该边是否加入到生成树中

        {

       v1=seeks(set,ge[i].bv);

           v2=seeks(set,ge[i].tv);

       if (v1!=v2)   // 当v1,v2不在同一集合,该边加入生成树

        {

          printf(“(%d,%d)”,ge[i].bv,ge[i].tv);

          set[v1]=v2;

          j++;

        }

       i++;

        }

   }


  ②  重(双)连通图和关节点
     
   若从一个连通图中删除任何一个顶点和其相关的边,它仍为一个
   连通图的话,则该连通图称为重(双)连通图

   若连通图中的某个顶点和其相关的边被删除之后,该连通图
   被分割成两个或两个以上的连通分量,则称此顶点为关节点

   ---->综上:【没有关节点的连通图为双(重)连通图】
   
   关节点的特征:【从深度优先生成树上找】
   假设从某个顶点V0出发对连通图进行深度优先搜索遍历,则可以得到
   一颗深度优先生成树,树上包含图的所有顶点
   1.若生成树的根节点,有两个或两个以上的分支,则此顶点必为关节点
   2.对生成树上的任意一个顶点,若其子树的根或子树中的其他顶点
        没有和其祖先想通的回边,则该顶点必为关节点!
   
   那如何求关节点就很重要了:


  
  ③  从源头到其余各点的最短路径【Dijkstra算法】:http://kjwy.5any.com/sjjg/index1.htm
   一、 单源点的最短路径问题:
   给定一个带权有向图D与源点v,求从v到D中其它顶点的最短路径。

 
   二、求最短路径
   Dijkstra提出按路径长度的递增次序,逐步产生最短路径的算法。首先求出长度最短的一条最短路径,再参照它求出长度次短的一条最短路径,依次类推,直到从顶点v到其它各顶点的最短路径全部求出为止。

   假设Dist[k]表示 当前所求得的从源点到顶点K的最短路径
   则一般情况下
   Dist[k] = <源点到顶点k的弧上的权值> 
   或者 Dist[k] = <源点到其他顶点的路径长度> + <其他顶点到该顶点k弧上的权值>
   【注意:此处的其他顶点,必定是已经求得最短路径的顶点】
  
   Min = 当前选出的最短路径长度
   //更新其他顶点的当前最短路径值
   if (!final[w] && (min + G.arcs[v][w] < Dist[w]))
   {
    Dist[w] = min + G.arcs[v][w];
    Path[w] = Path[v] + <v,w>;
   }

  ④ 求两个顶点间的最短路径【求每一对顶点间的最短路径】:
   一、每一对顶点之间的最短路径
   已知一个各边权值均大于0的带权有向图,对每一对顶点Vi≠Vj,要求求出Vi与Vj之间的最短路径和最短路径长度。

 
   二、求解每一对顶点之间的最短路径的方法
   一是采用迪杰斯特拉算法(Dijkstra),每次以一个顶点为源点,重复执行Dijkstra算法n次。这样,便可以求得每一对顶点之间得最短路径。总的执行时间为O(n3);

   二是采用弗洛伊德Floyd算法。

 

  ⑤  拓扑排序【解决有向图的问题:不允许出现回路(这个算法来检查是否有回路)】
   对有向图进行如下操作:按照有向图给出的次序关系,将图中顶点排成一个线性序列
   对于有向图没有限定次序关系的顶点,可以人为加上任意的次序关系,由此得到的顶点线性
   序列称为“拓扑有序序列”
   【得不出“拓扑有序序列”的有向图就是存在了回路!】

   如何进行拓扑排序:
   1.从有向图中选取一个没有前驱的顶点【即该顶点入度为0】,并输出
   2.从有向图中删除此顶点以及所有以它为尾的弧(删掉弧,是为了让弧头指向的顶点入度-1【代码就是让它的入度-1】)
   重复上述两步,直至图空【存在拓扑排序序列,无回路】,
   或者图不空但找不到无前驱的顶点【不存在拓扑排序序列,有回路】为止
    

     代码:

     部分算法:

     取入度为0的顶点v;
     while (v == 0)
     {
    printf(v);
    ++m;
    w = FirstAdj(v);
    while (w == 0)
    {
     inDegree[w]--; //入度-1
     w = nextAdj(v,w);
    }
    //取下一个入度为0的顶点v;
     }
     if(m < n) //取到的入度为0的个数m == 图中顶点个数就是没回路,否则有回路
   printf("图中有回路");


   完整算法:【利用栈来存储入度为0的顶点,那么出栈的时候要进行统计个数】
   CountInDegree(G, indegree);//对各顶点求入度
   InitStack(S);
   for (i=0; i<G.vexnum; ++i)
   {
    if (!indegree[i]) //如果入度为0
     Push(S, i); //放入栈中
   }
   count = 0; //对输出顶点计数
   while (!EmptyStack(S))
   {
    Pop(S, v);
    ++count; 
    printf(v);
    for (w = FirstAdj(v); w; w=NextAdj(G, v, w))
    {  //扫描刚出栈的v的所有邻接点,将它们的入度-1
     --indegree[w];
     if (!indegree[w])
     { //如果入度-1之后,入度为0了,那么也就可以入栈了
      Push(S, w);
     }
    }
   }
   if (count < G.vexnum) //如果出栈(或入栈)的数目不足图中顶点个数
    printf("图中有回路");
  
  
  
  ⑥   关键路径
       引出问题: 弧上的权值表示完成工程所需的时间,哪些工序是完成这个工程的关键
      即:影响整个工程完成期限的子工程
   【整个工程完成的时间:从有向图的源点到汇点的最长路径】
   【“关键活动”是该弧上的权值增加将使得有向图上最长路径的长度增加(总工程时间增加)】

   如何求“关键活动”:【根据“拓扑有序序列”的次序来求】
   “事件(顶点)”的最早发生事件 ve(j) = 从源点到顶点j的最长路径长度
   “事件(顶点)”的最迟发生事件 vl(k) = 从顶点k到汇点的最短路径长度
    假设第i条弧为<j,k>
    则第i项活动: "活动弧"的最早开始时间 ee(i) = ve(j);
        "活动弧"的最迟开始时间 el(i) = vl(k) - dut(<j,k>)【减去弧的权值】
    事件发生的时间计算公式:
    ve(源点) = 0;
    ve(k) = Max{ve(j) + dut(<j,k>)}

    vl(汇点) = ve(汇点);
    vl(j) = Min{vl(k) - dut(<j, k>)}

 

   

 


    算法的实现要点:
    求ve的顺序应该按拓扑有序的次序,而求vl的顺序要按拓扑逆序的次序
    【拓扑排序的时候用"栈"记下拓扑有序序列,到时候出栈就可以得到拓扑逆序序列了】
*
*
*
*
*
*
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值