最短路径

6.6.2最短路径

​ 假若要在计算机上建立一个交通咨询系统,则可以采用图的结构来表示实际的交通网络。如 图 6.21 所示,图中顶点表示城市,边表示城市间的交通联系。例如,一位旅客要从 A 城到 B 城, 他希望选择一条中转次数最少的路线。假设图中每一站都需要换车,则这个问题反映到图上就是 要找一条从顶点 A 到 B 所含边的数目最少的路径。只需从顶点 A 出发对图做广度优先搜索,一 旦遇到顶点B就终止。由此所得的广度优先生成树上,从根顶点A到顶点B的路径就是中转次 数最少的路径,路径上A与B之间的顶点数就是中转次数,但是,这只是一类最简单的图的最 短路径问题。有时,对于旅客来说,可能更关心的是节省交通费用;而对千司机来说,里程和 速度则是他们感兴趣的信息。为了在图上表示有关信息,可对边赋以权,权的值表示两城市间的 距离,或途中所需时间,或交通费用等。此时路径长度的度最就不再是路径上边的数目,而是路 径上边的权值之和。考虑到交通图的有向性,例如,汽车的上山和下山,轮船的顺水和逆水,所 花费的时间或代价就不相同,所以交通网往往是用带权有向网表示。在带权有向网中, 习惯上称 路径上的第一个顶点为源点(Source), 最后一个顶点为终点(Destination)。

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

算法6.10 迪杰斯特拉算法

  • 算法步骤

    1.初始化:

    • 将源点 Vo加到S中,即S[v0] = true ;
    • 将 v。到各个终点的最短路径长度初始化为权值,即 D[i] = Garcsv0, (vi属于V-S);
    • 如果 v。和顶点v之间 有弧,则将 Vi的前驱置为 Vo, 即 Path[i] = v0 , 否则 Path[i] = -1。

    2.循环n-1次,执行以下操作:

    • 选择下一条最短路径的终点 Vk, 使得:D[k] = Min
    • 将 Vk加到S中,即S[vk] =true;
    • 根据条件更新从 v0出发到集合 v-s 上任一顶点的最短路径的长度,若条件D[k] + Gares [k] [i]<D[i]成立,则更新 D[i] = D[k] +Garcs[k] [i], 同时更改 Vi的前驱为vk; Path [i] = k。
  • 算法描述

    void ShortestPath_DIJ(AMGraph G, int vO)
    {//用Dijkstra算法求有向网G的vO顶点到其余顶点的最短路径
        n=G.vexnum;                          //n为G 中顶点的个数
        for(v= O;v<n; ++v)                   //n个顶点依次初始化
        {
            S[v]=false;                      //S初始为空集
            D[v]=G.arcs[vO][v];              //将vO到各个终点的最短路径长度初始化为弧上的权值
            if(D[v]<Maxlnt) Path[v]=vO;      //如果V0和V之间有弧,则将V的前驱置为V0
            else Path[v]=-1                  //如果vO 和v之间无弧, 则将v的前驱置为一1
        }
        S[vO] = true;                        //将V0加入S
        D[vO]=0;                            //源点到源点的距离为0
     /*-----初始化结束, 开始主循环, 每次求得vO到某个顶点v的最短路径, 将v加到s集----*/
        for( i=l; i<n;++i)                   //对其余 n-1个顶点,依次进行计算
        {
            min= Maxlnt; 
            for(w= O;w<n;++w)
            if (!S[w] &&D [w] <min)
            {v=w;min=D[w];}                 //选择一条当前的最短路径,终点为v
         S[v]=true;                         //将v加入S
         for(w=O;w<n;++w)                   //更新从v。出发到集合v-s上所有顶点的最短路径长度
             if (! S [w) && (D [v) +G. arcs [v) [w) <D [w])) 
           {
               D[w] =D [v] +G.arcs[v][w];      //更新 D[w]
               Path[w]=v;                      //更改w的前驱为v
           }
        }
    }
    
    

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

求解每一对顶点之间的最短路径有两种方法:其一是分别以图中的每个顶点为源点共调用n次 迪杰斯特拉算法;其二是采用下面介绍的弗洛伊德(Floyd)算法。 两种算法的时间复杂度均为O(n3 ), 但后者形式上较简单。

弗洛伊德算法仍然使用带权的邻接矩阵 arcs 来表示有向网G,求从顶点V; 到VJ的最短路径。算法的实现要引入以 下辅助的数据结构。

(1) 二维数组 Path[i] [j]:最短路径上顶点 VJ 的前一顶点的序号。

(2)二维数组D[i] [j]:记录顶点 Vi和vj之间的最短路径长度。

算法6.11 费罗伊德算法

  • 算法描述

    void ShortestPath_Floyd(AMGraph G)
    {//用Floyd算法求有向网G中各对顶点1和)之间的最短路径
    for (i=O; i < G. vexnum; ++i)        //各对结点之间初始已知路径及距离
    for(j=O;j <G.vexnum;++j)
    {
      D[i][j] =G.arcs [i][j];
      if(D[i] [j]<Maxint) Path[i] [j]=i; //如果i和j之间有弧,则将j的前驱置为1.
      else Path[i] [j]=-1;               //如果i和j之间无弧,则将j的前驱置为-1
    }
    for (k=O; k < G. vexnum; ++k) 
     for (i=O; i <G.vexnum;++i) 
      for(j=O;j <G.vexnum;++j)
       if(D[i] [k]+D[k][j] <D[i][j])     //从i经k到j的一条路径更短
        { 
         D[i][j]=D[i][k]+D[k][j];        //更新D[i][j]
         Path[i][j]=Path[k][j];          //更改j的前驱为K
        }
    }
    

6.6.3拓扑排序

1.AOV-网

一个无环的有向图称作有向无环图( DirectedAcycline Graph), 简称DAG图。有向无环图是描述一项工程或系统的进行过程的有效工具。通常把计划、 施工过程、 生产流程、 程序流程等都当成一个工程。除了很小的工程外,一般的工程都可分为若干个称做活动(Activity)的子工程,而这些子工程之间, 通常受着一定条件的约束, 如其中某些子工程的开始必须在另一些子工程完成之后。

2.拓扑排序的过程

(1) 在有向图中选一个无前驱的顶点且输出它。

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

(3) 重复 (1) 和 (2), 直至不存在无前驱的顶点。

(4)若此时输出的顶点数小千有向图中的顶点数,则说明有向图中存在环, 否则输出的顶点序列即为一个拓扑序列。

3.拓扑排序的实现

(1)一维数组 indegree[i]: 存放各顶点入度,没有前驱的顶点就是入度为零的顶点。 删除顶点及以它为尾的弧的操作,可不必真正对图的存储结构进行改变,可用弧头顶点的入度减l的办法来实现。

(2)栈S: 暂存所有入度为零的顶点,这样可以避免重复扫描数组indegree检测入度为0的顶点, 提高算法的效率。

(3)一维数组topo(i]: 记录拓扑序列的顶点序号。

算法6.12 拓扑排序

  • 算法步骤

    • 求出各顶点的入度存入数组 indegree[i]中, 并将入度为0 的顶点入栈。
    • 只要栈不空, 则重复以下操作:
      • 将栈顶顶点 vi出栈并保存在拓扑序列数组 topo 中;
      • 对顶点 V;的每个邻接点 Vk的入度减1, 如果 Vk的入度变为0, 则将 Vk入栈。
    • 如果输出顶点个数少于AOV-网的顶点个数, 则网中存在有向环, 无法进行拓扑排序, 否则拓扑排序成功。
  • 算法描述

    Status TopologicalSort(ALGraph G,int topo[]) 
    {//有向图G采用邻接表存储结构
    //若 G 无回路,则生成 G 的一个拓扑序列 topo []并返回 OK, 否则 ERROR
        FindinDegree(G,indegree);           //求出各顶点的入度存入数组 indegree中
        InitStack(S)                        //栈 s初始化为空
        for(i=O;i<G.vexnum;++i) 
            if (! indegree [i)) Push (S, i);//入度为0者进栈
        m=O;                                //对输出顶点计数,初始为0
        while (! StackEmpty (S))            //栈s非空
        {
            Pop (S, i);                     //将栈顶顶点Vi出栈
            topo[m]=i;                      //将Vi保存在拓扑序列数组 topo中
            ++m;                            //对输出顶点计数
            p=G.vertices[i) .firstarc;      //p指向Vi的第一个邻接点
            while (p!=NULL) 
            {
                k=p->adjvex;                //vk为 m 的邻接点
                --indegree[k];              //vi的每个邻接点的入度减1
                if(indegree[k)==O) Push(S,k); //若入度减为0, 则入栈
                p=p->nextarc;               //vi的每个邻接点的入度减1
             }
         }
         if(m<G.vexnum) return ERROR;       //该有向图有回路
         else return OK; 
    }
    
    

6.6.4关键路径

1.AOE-网

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

2.关键路径求解的过程

(1)对图中顶点进行排序, 在排序过程中按拓扑序列求出每个事件的最早发生时间ve(i)。

( 2 )按逆拓扑序列求出每个事件的最迟发生时间vl(i)。

(3) 求出每个活动 a;的最早开始时间e(i)。

(4) 求出每个活动 a;的最晚开始时间l(i)-

( 5 )找出 e(i)= l(i)的活动a;, 即为关键活动。由关键活动形成的 由源点到汇点 的每一条路径就是关键路径, 关键路径有可能不止一条 。

3.关键路径算法的实现

算法6.13 关键路径算法

  • 算法描述

    Status CriticalPath(ALGraph G) 
    {//G为邻接表存储的有向网,输出G的各项关键活动
        if(!TopologicalOrder(G,topo)) return ERROR; 
        //调用拓扑排序算法,使拓扑序列保存在topo中,若调用失败, 则存在有向环, 返回ERROR
        n=G. vexnum;                  //n为顶点个数
        for(i=O;i<n; i++)            //给每个事件的最早发生时间置初值0
    /*- - - ----- -- - 按拓扑次序求每个事件的最早发生时间 - - - - - - ----*/
        for (i=O; i<n; i++)
        {
            k=topo[i];                //取得拓扑序列中的顶点序号K
            p=G.vertices[k].firstarc; //p指向k的第一个邻接顶点
            while(p!=NULL);
            {                         //依次更新k的所有邻接顶点的最早发生时间
                j=p->adjvex;          //j为邻接顶点的序号
                if(ve[j]<ve[k]+p->weight)//更新顶点j的最早发生时间 ve[j]
                    ve[j]=ve[k]+p->weight;
                p=p->nextarc;         //p指向k的下一个邻接顶点
             }
         }
         for(i=O;i<n;i++)             //给每个事件的最迟发生时间置初值 ve[n-1]
            vl[i]=ve[n-1];
    /*-------------按逆拓扑次序求每个事件的最迟发生时间--- ----- ------*/ 
         for(i=n-l;i>=O;i--) 
         {
             k=topo[i];               //取得拓扑序列中的顶点序号K
             p=G.vertices[k) .firstarc;//p指向k的第一个邻接顶点
             while(p!=NULL)           //根据k的邻接点,更新k的最迟发生时间
             {
                 j=p->adjvex;         //j为邻接顶点的序号
                 if(vl[k]>vl[j)-p->weight) //更新顶点 K 的最迟发生时间 vl [k]
                     vl[k)=vl[j)-p->weight; 
                 p=p->nextarc;        //p指向k的下一个邻接顶点
              }
           }
    /*---------- - - - -- -判断每一活动是否为关键活动-------*/
           for(i=O;i<n;i++)           //每次循环针对m为活动开始点的所有活动
           {
               p=G.vertices[i) .firsarc;//p指向1的第一个邻接项点
               while (p ! =NULL) 
               {
                   j=p->adjvex;       //j为i的邻接顶点的序号
                   e=ve[i];           //计算活动<Vi, Vj>的最早开始时间
                   l=vl[j)-p->weight; //计算活动<Vi, Vj>的最迟开始时间
                   if (e==l)          //若为关键活动,则输出<vi, Vj>
                        cout<<G.vertices[i].data<<G.vertices[j] .data; 
                    p=p->nextarc;     //p指向 i 的下一个邻接顶点
               }
           }
    }
    
    
posted @ 2022-02-11 13:58  在结束时开始  阅读(333)  评论(0)    收藏  举报