关于关键路径的一些个人理解

在之前的一篇文章,提到了拓扑排序的相关概念,其实拓扑排序所操作的对象类型就是AOV网。

先介绍一下AOV网和AOE网的相关概念:

AOV网,也就是顶点活动,就是前面文章所阐述的一种有向无环图。其节点代表活动,使用边来代表节点之间的序列关系;

AOE网,也就是边活动,其用边来代表具体的活动,并且用定点代表事件,其中边权代表完成活动的时间。这里要区分事件和活动这一概念。这里的事件更像是一种标志,标志到这个节点时,之前的活动都已经结束,可以进行下一个活动,就像书中所说,其代表的是一个中介状态。对于AOE网来说,所有的起始时间和最终事件都可以归为一个点。

此时,就引入关键路径的概念;

所谓关键路径,就是AOE网中最长的路径,关键路径上的活动称为关键活动,关键活动就是不允许拖延的活动。关键路径下的时长也成为最短时长。这里需要注意一点,活动的进行是多线程的,并不是单线程,所以在多线程情况下的最短时间映射的就是关键路劲,也就是图中的最长路径,这样谈这个概念才是有意义的,否则是无意义的;

所以本质上关键路径的求解就是对于有向无环图DAG中最长路径的求解方法;

由于活动时具有拖延性的,每一个活动都有最早的发生时间和最晚发生时间,其时差就可以任意安排的时间。因此对于关键路径中的关键活动,其标志性的特点就是:最早发生时间=最晚发生时间;

对该条件进行抽象,就是对于两个数组e[r],l[r],分别代表相应活动的最早发生时间和最晚发生时间,其中当e[i]=l[i],就代表该活动位关键活动,也就是关键路径上的一条边;

接下来关键的就是如何对这两个数组进行求解:

在书上,求解方法是构造节点的相应数组,来进行数组e,l的求解;

由于节点作为时间,也有拖延的可能性,其也存在最早和最晚时间。所以事件的最早开始时间就是上一个活动的最早结束时间;事件的最晚开始事件就是新活动的最迟开始时间。

所以对于数组e,l,求解就转换为了对另外两个数组的求解:ve,vl;

如上图所示:

对于活动Ar来说,其最早开始时间就是事件Vi的最早开始时间,因此:e[r]=ve[i];

活动事件Vj的最晚开始时间取决于活动Ar的最晚开始时间,也就是vl[j]=l[r]+length[r],也就是l[r]=vl[j]-length[r];

此时,e,l数组的求解也就变成了vl,ve数组的求解;

对于数组vl,ve,可以借助前驱节点和后继节点来进行判断;

对于ve数组,也就是最早发生数组,可以采用如下的判断方式:

如上所示,如果Vj要发生,则前驱节点必须要全部发生,并且前驱结点的发生时间也为最早发生时间。由于各个路线的权值不一样,也就是活动时长不一样,所Vj的发生时间必为Max(ve[ip]+length[rp]);

所以这种判定方式的实现也十分简单,只要使用拓扑排序就可以保证访问Vj使前面的所有节点都进行过访问;对于代码上的逻辑实现,我们每次找到一个节点之后,进行到后继节点的比较,始终让他更新后继节点的值,从而使得遍历完i前驱节点之后,j节点的值已经是Max(ve[ip]+length[rp]);

相关的判定代码如下所示:

stack<int> topOrder;//拓扑序列
bool topologicalSort(){
    queue<int>q;
    for(int i=0;i<n;i++){
        if(inDegree[i]==0){
            q.push(i);
        }
    }
    while(!q.empty()){
        int u=q.front();
        q.pop();
        topOrder.push(u)//加入拓扑序列;
        for(int i=0;i<G[u].size();i++){
            int v=G[u][i].v;
            inDegree[v]--;
            if(inDegree[v]==0){
                q.push(v);
            }
            if(ve[u]+G[u][i].w>ve[v]){
                ve[v]=ve[u]+G[u][i].w;
            }
        }
    }
    if(topOrder.size()==n)
        return true;
    else
        return false;
}

其中值得更新后继节点的就是该部分:

注意,这里G是一个二维数组,G[u]代表的是u节点,G[u]表示一个数组,都为u的后继节点;

使用上述代码就可以使得ve[j]的值由不同的ix节点遍历时,不断进行更新。当ix节点全部访问完就可以将其入队列,访问j节点;

接下来就是对vl数组进行构造和操作:

vl数组的公式推导为:l[r]=vl[j]-length[r]

由于vl数组代表的当前事件发生的最晚时间,对于该问题求解,我们可以转换为向前求解,也就是j事件的最晚事件-活动时间的最小值,就是i节点的最晚发生时间,也就是:Min{vl[jp]-length[rp]}

vl数组所对应的情况如下所示:

因此,vl数组的操作必须要先知道后继节点的值。对于该问题,我们可以使用逆拓扑推导,但是这里时根据ve数组压栈得到的逆序列,所以很容易实现;

大致代码如下所示:

fill(vl,vl+n,ve[n-1]);//使用终点的ve值
while(!topOrder.empty()){
    int u=topOrder.top();
    topOrder.pop();
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i].v;
        if(vl[v]-G[u][i].w<vl[u]){
            vl[u]=vl[v]-G[u][i].w;
        }
    }
}

注意一点,先进行正拓扑排序,在进行逆拓扑排序,并且第二次使用的最后初值为ve的最终值;

当vl和ve数组求解完毕,就可以根据公式来进行e和v的求解。

注意,这里也要分清一个问题:e和v数组针对的时边,也就是活动,而ve,vl数组针对的时点,两者不同。

for(int u=0;u<n;u++){
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i].v,w=G[u][i].w;
        int e=ve[u];
        l=vl[v]-w;
        if(e==l){
            //说明时关键活动,也就是关键路径
            //输出
        }
    }
}

当然,如果只需要求最长的路径长度,直接取ve数组的最大值就可以,因为ve数组的最大值代表的必定是最后一个事件发生的最早时间(也就是完成事件的最早发生时间),所以也就是最快的事件,最长路径的长度;

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值