图的关键路径

关键路径的算法是建立在拓扑排序的基础之上的,这个算法中用到了拓扑排序,所以在这里先以拓扑排序开篇。

1. 什么是拓扑排序?
举个例子先:一个软件专业的学生学习一系列的课程,其中一些课程必须再学完它的基础的先修课程才能开始。如:在《程序设计基础》和《离散数学》学完之前就不能开始学习《数据结构》。这些先决条件定义了课程之间的领先(优先)关系。这个关系可以用有向图更清楚地表示。图中顶点表示课程,有向边表示先决条件。若课程i是课程j的先决条件,则图中有弧(i,j)。若要对这个图中的顶点所表示的课程进行拓扑排序的话,那么排序后得到的序列,必须是按照先后关系进行排序,具有领先关系的课程必然排在以它为基础的课程之前,若上例中的《程序设计基础》和《离散数学》必须排在《数据结构》之前。进行了拓扑排序之后的序列,称之为拓扑序列。
2. 如何实现拓扑排序?
很简单,两个步骤:
1. 在有向图中选一个没有前驱的顶点且输出。
2. 从图中删除该顶点和以它为尾的弧。
重复上述两步,直至全部顶点均已输出,或者当前图中不存在无前驱的顶点为止。后一种情况则说明有向图中存在环。
3. 什么是关键路径?
在学习关键路径前,先了解一个AOV网和AOE网的概念:
图的关键路径_第1张图片
用顶点表示活动,用弧表示活动间的优先关系的有向图:
称为顶点表示活动的网(Activity On Vertex Network),简称为AOV网。
与AOV网对应的是AOE(Activity On Edge)网即边表示活动的网。
AOE网是一个带权的有向无环图。
网中只有一个入度为零的点(称为源点)和一个出度为零的点(称为汇点)。
其中,顶点表示事件(Event),弧表示活动,权表示活动持续的时间。
通常,AOE网可用来估算工程的完成时间。
假如汽车生产工厂要制造一辆汽车,制造过程的大概事件和活动时间如上图AOE网:
我们把路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径,在关键路径上的活动叫关键活动。
那么,显然对上图AOE网而言,所谓关键路径:
开始–>发动机完成–>部件集中到位–>组装完成。路径长度为5.5。
如果我们试图缩短整个工期,去改进轮子的生产效率,哪怕改动0.1也是无益的。
只有缩短关键路径上的关键活动时间才可以减少整个工期的长度。
例如如果制造发动机缩短为2.5天,整车组装缩短为1.5天,那么关键路径为4.5。
工期也就整整缩短了一天时间。
好吧! 那么研究这个关键路径意义何在?
假定上图AOE网中弧的权值单位为小时,而且我们已经知道黑深色的那一条为关键路径。
假定现在上午一点,对于外壳完成事件而言,为了不影响工期:
外壳完成活动最早也就是一点开始动工,最晚在两点必须要开始动工。
最大权值3表示所有活动必须在三小时之后完成,而外壳完成只需要2个小时。
所以,这个中间的空闲时间有一个小时,为了不影响整个工期,它必须最迟两点动工。
那么才可以保证3点时与发动机完成活动同时竣工,为后续的活动做好准备。
对AOE网有待研究的问题是:
(1)完成整个工程至少需要多少时间?
(2)那些活动是影响工程进度的关键?
今天研究是实例如下图所示:
图的关键路径_第2张图片
假想是一个有11项活动的AOE网,其中有9个事件(V1,V2,V3…V9)。
每个事件表示在它之前的活动已经完成,在它之后的活动可以开始。
如V1表示整个工程开始,V9表示整个共结束,V5表示a4和a5已经完成,a7和a8可以开始。
关键路径算法
为了更好的理解算法,我们先需要定义如下几个参数:
(1)事件的最早发生时间etv(earliest time of vertex): 即顶点Vk的最早发生时间。
(2)事件的最晚发生时间ltv(latest time of vertex): 即顶点Vk的最晚发生时间。也就是每个顶点对应的事件最晚需要开始的时间,超出此时间将会延误整个工期。
(3)活动的最早开工时间ete(earliest time of edge): 即弧ak的最早发生时间。
(4)活动的最晚开工时间lte(latest time of edge): 即弧ak的最晚发生时间,也就是不推迟工期的最晚开工时间。
然后根据最早开工时间ete[k]和最晚开工时间lte[k]相等判断ak是否是关键路径。
将AOE网转化为邻接表结构如下图所示:
图的关键路径_第3张图片
求事件 的最早发生时间etv的过程,就是我们从头至尾找拓扑序列的过程,因此,在求关键路径之前,需要先调用一次拓扑序列算法来计算etv和拓扑序列列表。为此我们首先在程序开始处声明几个全局变量。

int  *etv,*ltv;//事件最早发生时间和最迟发生时间数组
int  *stack2;//用于存储拓扑序列的栈
int   top2; //用于stack2的指针
//下面是改进过的求拓扑序列算法
status  TopologicalSort(GraphAdjList   GL)
{
      EdgeNode   *e;
      int   i,k,gettop;
      int   top=0;    //用于栈指针下标
      int    count=0;   //用于统计输出顶点的个数
      int     *stack;  //创栈将入度为0的顶点入栈
     stack=(int  *)malloc(GL->numVertexes*sizeof(int));
     for(i=0;inumVertexes;i++)
          if(0==GL->adjList[i].in) stack[++top]=i;
      top2=0;
     etv=(int  *)malloc(GL-numVertexes*sizeof(int)); //事件最早发生时间
      for(i=0;inumVertexes;i++)
        etv[i] = 0;
     stack2=(int  *)malloc(GL->numVertexes*sizeof(int));
    while(top!=0)
    {
        gettop = stack[top--];
       count++;
       stack2[++top2] = gettop; //将弹出的顶点序号压入拓扑序列的栈
        for(e=GL->adjList[gettop].firstedge;e;e=e->next)
        {
                k=e->adjvex;
               if(!(--GL->adjList[k].in))
                       stack[++top]=k;
             if((etv[gettop]+e->weight)>etv[k])
                        etv[k]=etv[gettop]+e->weight;
         }
    }
        if(countnumVertexes)
            return ERROR;
        else
            return OK;
}

第11~15行为初始化全局变量etv数组、top2和stack2的过程。第21行就是将本是要输出的拓扑序列压入全局栈stack2中。第27~28行很关键,它是求etv数组的每一个元素的值。比如说,假如我们已经求得顶点v0对应的etv[0]=0,顶点v1对应的etv[1]=3,顶点v2对应的etv[2]=4,现在我们需要求顶点v3对应的etv[3],其实就是求etv[1]+len(v1,v3)与etv[2]+len(v2,v3)的较大值。显然3+5<4+8,得到etv[3]=12。如图所示,在代码中e->weight就是当前弧的长度。
图的关键路径_第4张图片
下面我们来看一下求关键路径的算法代码
下面我们来看一下求关键路径的算法代码
void CriticalPath(GraphAdjList GL)

{
        EdgeNode   *e;
        int   i,gettop,k,j;
        int ete,lte;
       TopologicalSort(GL);   //求拓扑序列,计算数组etv和stack2的值
       ltv=(int  *)malloc(GL->numVertexes*sizeof(int));//事件最晚发生时间
       for(i=0;inumVertexes;i++)
                   ltv[i] =etv[GL->numVertexes-1];//初始化ltv
       while(top2!=0)
       {
                 gettop = stack2[top2-1];
                 for(e=GL->adjList[gettop].firstedge;e;e=e->next)
                 {//求各顶点事件的最迟发生时间ltv值
                       k=e->adjvex;
                      if(ltv[k]-e->weightweight;
                 }
                  for(j=0;jnumVertexes;j++)
                 {
                         for(e=GL->adjList[j].firstedge;e;e=e->next)
                         {
                                k=e->adjvex;
                                ete = etv[j];
                                lte =ltv[k]-e->weight;
                                if(ete==lte)
                                   printf(“%d,v%d>length:%d,”,GL->adjList[j].data,GL->adjList[k].data,e->weight);
                          }
                  }
        }
}

图的关键路径_第5张图片
图的关键路径_第6张图片
图的关键路径_第7张图片

你可能感兴趣的:(数据结构)