图的关键路径

转自:
http://blog.sina.com.cn/s/blog_51b6521b0100k96i.html

(一) AOE网

事件  含义

    v1    开工

    v2    活动a1完成,活动a4可以开始

    v3    活动a2完成,活动a5可以开始

    v4    活动a3完成,活动a6可以开始

    v5    活动a4与a5完成,活动a7和a8可开始,

    v6    活动a6完成,活动a9可以开始

    v7    活动a7完成,活动a10可以开始

    v8    活动a8与a9完成,活动a11可以开始

    v9    活动a10和a11完成,整个工程完成

   边(弧):代表活动(操作);

   边权:代表活动的持续时间。记边ak=的权为len(ak)或len(i,j);

   结点:代表事件(状态)。它表示它的各入边代表的活动均已完成,而它的出边代表的活动可以开始。

事实上,某结点代表的事件是它的各入边代表的活动的共同作用结果,同时也是它的各出边代表的活动的启动条件。AOE网中没有入边的结点称为始点,没有出边的结点称为终点。AOE一般用来描述工程进度,结点表示工程进展中的状态,边表示子任务。图 21‑4就是一个AOE网,它可以看作是一个具有11项子任务和9个状态的假想工程的进度图。

 

(二) AOE网的操作

 

      针对AOE网的操作一般有下列几种:

      关键路径CPM(Critical Path Method)。 这种操作最早用于维修与建筑行业中工期进度估算。

      性能估计与复审PERT(Performance Evaluation and Review Technique):该项操作最初是为了研制北极星式导弹系统而引入的。

      资源分配与多工程调度RAMPS(Resource Allocation  and  Multi-Project Scheduling)

 

(三) 关键路径的若干基本概念

 

下面的阐述中,设AOE网的起点为v0终点为vn.

1.关键路径

AOE网中,从事件i到j的路径中,加权长度最大者称为i到j的关键路径(Critical Path),记为cp(i,j)。特别地,始点0到终点n的关键路径cp(0,n)是整个AOE的关键路径。

显然,关键路径决定着AOE网的工期,关键路径的长度就是AOE网代表的工程所需的最小工期。

2.事件最早/晚发生时间

事件vi的最早发生时间ve(i)定义为:从始点到vi的最长(加权)路径长度,即cp(0,i)

事件vi的最晚发生时间vl(i)定义为:在不拖延整个工期的条件下,vi的可能的最晚发生时间。即vl(i) = ve(n) - cp(i, n)

3.活动最早/晚开始时间

活动ak=的最早开始时间e(k):等于事件vi的最早发生时间,即

     e(k) = ve(i) = cp(0, i)

活动ak=的最晚开始时间l(k)定义为:在不拖延整个工期的条件下,该活动的允许的最迟开始时间,即

        l(k) = vl(j) – len(i, j)

这里,vl(j)是事件j的允许的最晚发生时间,len(i, j)是ak的权。

活动ak的最大可利用时间:定义为l(k)-e(k)

 4.关键活动

若活动ak的最大可利用时间等于0(即(l(k)=e(k)),则称ak 为关键活动,否则为非关键活动。

显然,关键活动的延期,会使整个工程延期。但非关键活动不然,只要它的延期量不超过它的最大可利用时间,就不会影响整个工期。

关键路径的概念,也可以用这里的关键活动定义,即有下面的:

(一) 基本算法

    关键路径算法是一种典型的动态规划法,这点在学了后面的算法设计方法后就会看到。下面就来介绍该算法。设图G=(V, E)是个AOE网,结点编号为1,2,...,n,其中结点1与n 分别为始点和终点,ak=∈E是G的一个活动。

 

根据前面给出的定义,可推出活动的最早及最晚发生时间的计算方法:

  e(k) = ve(i)

  l(k) = ve(j) - len(i,j)  

结点的最早发生时间的计算,需按拓扑次序递推:

                 ve(1) = 0

                 ve(j) = MAX{ ve(i)+len(i, j) } 

对所有 ∈E的i  结点的最晚发生时间的计算,需按逆拓扑次序递推:

                 vl(n) = ve(n)

                 vl(i) = MIN{vl(j) - len(i, j)} 对所有∈E的j 

关于 ve与vl的求法,可参阅图 21‑5。

这种计算方法, 依赖于拓扑排序, 即计算ve( j) 前,应已求得j 的各前趋结点的ve值,而计算vl(i)前,应已求得i的各后继结点的vl值。ve的计算可在拓扑排序过程中进行,即在每输出一个结点i后,在删除i的每个出边(即入度减1)的同时,执行

                if ( ve[i]+len(i,j)) > ve[j] ) 

                ve[j] = ve[i] + len(i,j) 

实际上,该操作对i的每个后继j分别进行一次。因此对程序作少量扩充即可求得ve。

vl的值可按类似的方法在逆拓扑排序过程(即直接按与拓扑序列相反的次序输出结点的过程)中求得,但一般不必专门这样进行。事实上,通过逆方向使用拓扑序列即可递推出各vl的值,假定拓扑序列是topoSeq,则vl 的值的求法为(结点编号为1~n)。

       

 for (k=1; k<=n; k++)  vl[k] = ve[n];   //初始化
        for ( k=n; k>=1; k--)
        {
           i=topoSeq[k];
           j=(i的第1个出点);
           while (j存在)
          {
             if (vl[j]-len(i,j)

求图21-6 的AOE网的所有事件的最早发生时间ee();所有事件的最迟发生时间le();每项活动ai的最早开始时间e()和最迟开始时间l(),完成此工程最少需要多少天?那些是关键活动,是否存在某项活动,当其提高速度后能使整个工程缩短工期?

存储结构及算法设计

1、在结点的定义中增加ee和le 字段用于记录个事件的最早开始时间和最迟开始时间,同时得到关键路径和最小工期

2、邻接矩阵中使用边权代替原来连接标志1

3、进行拓扑排序,形成拓扑序列

1 2 3 4 5 6 7 8 9

4、按拓扑序列顺序,从前向后搜索寻找个活动(即边),若存在该活动,则计算相应事件的最早开始时间。

5、按拓扑序列顺序,从后向前搜索寻找个活动(即边),若存在该活动,则计算相应事件的最迟开始时间。

6、计算各活动的最早开始时间和最迟开始时间

算法源程序:

#include 
#include 
#define MaxVerNum 20
 
int visited[MaxVerNum];
 
typedef char VertexType;
 
typedef struct ArcNode
{
       int adjvex;                                         //该弧指向的顶点位置
       struct ArcNode * nextarc;          //指向下一个表结点
       int info;                                      //权值信息
}ArcNode;                                              //边结点类型
 
typedef struct VNode
{
       VertexType data;
       int indegree;
       ArcNode * firstarc;
}VNode, Adjlist[MaxVerNum];
 
typedef struct
{
       Adjlist vertices;            //邻接表
       int vernum, arcnum;             //顶点数和弧数
}ALGraph;
 
//查找符合的数据在数组中的下标
int LocateVer(ALGraph G, char u)
{
       int i;
       for(i = 0; i < G.vernum; i++)
       {
              if(u == G.vertices[i].data)
                     return i;
       }
       if(i == G.vernum)
       {
              printf("Error u!\n");
              exit(1);
       }
       return 0;
}
 
//常见图的邻接矩阵
void CreateALGraph(ALGraph &G)
{
       int i, j, k, w;
       char v1, v2;
       ArcNode * p;
       printf("输入顶点数和弧数: ");
       scanf("%d %d", &G.vernum, &G.arcnum);
       printf("请输入顶点!\n");
       for(i = 0; i < G.vernum; i++)
       {
              printf("请输入第 %d 个顶点: \n", i);
              fflush(stdin);
              scanf("%c", &G.vertices[i].data);
              G.vertices[i].firstarc = NULL;
              G.vertices[i].indegree = 0;
       }
 
       for(k = 0; k < G.arcnum; k++)
       {
              printf("请输入弧的顶点和相应权值(v1, v2, w): \n");
              //清空输入缓冲区
              fflush(stdin);
              scanf("%c %c %d", &v1, &v2, &w);
              i = LocateVer(G, v1);
              j = LocateVer(G, v2);
              p = (ArcNode *)malloc(sizeof(ArcNode));
              p->adjvex = j;
              p->info = w;
              p->nextarc = G.vertices[i].firstarc;
              G.vertices[i].firstarc = p;
              G.vertices[j].indegree++;                   //vi->vj, vj入度加1
       }
       return;
}
 
//求图的关键路径函数
void CriticalPath(ALGraph G)
{
       int i, k, e, l;
       int * Ve, * Vl;
       ArcNode * p;
 
       //*****************************************
       //以下是求时间最早发生时间
       //*****************************************
 
       Ve = new int [G.vernum];
       Vl = new int [G.vernum];
 
       for(i = 0; i < G.vernum; i++)              //前推
              Ve[i] = 0;
 
       for(i = 0; i < G.vernum; i++)
       {
              ArcNode * p = G.vertices[i].firstarc;
              while(p != NULL)
              {
                     k = p->adjvex;
                     if(Ve[i] + p->info > Ve[k])
                            Ve[k] = Ve[i]+p->info;
                     p = p->nextarc;
              }
       }
       //*****************************************
       //以下是求最迟发生时间
       //*****************************************
       for(i = 0; i < G.vernum; i++)
              Vl[i] = Ve[G.vernum-1];
       for(i = G.vernum-2; i >= 0; i--)                 //后推
       {
              p = G.vertices[i].firstarc;
              while(p != NULL)
              {
                     k = p->adjvex;
                     if(Vl[k] - p->info < Vl[i])
                            Vl[i] = Vl[k] - p->info;
                     p = p->nextarc;
              }
       }
       //******************************************
       for(i = 0; i < G.vernum; i++)
       {
              p = G.vertices[i].firstarc;
              while(p != NULL)
              {
                     k = p->adjvex;
                     e = Ve[i];              //最早开始时间为时间vi的最早发生时间
                     l = Vl[k] - p->info;             //最迟开始时间
                     char tag = (e == l) ? '*' : ' '; //关键活动
                     printf("(%c, %c), e = %2d, l = %2d, %c\n", G.vertices[i].data, G.vertices[k].data, e, l, tag);
                     p = p->nextarc;
              }
       }
       delete [] Ve;
       delete [] Vl;
}
 
void main()
{
       ALGraph G;
       printf("以下是查找图的关键路径的程序。\n");
       CreateALGraph(G);
       CriticalPath(G);
}

你可能感兴趣的:(算法)