【图论】关键路径

一、AOV网和AOE网

AOE网和AOV网都是有向无环图,

如果给定AOV网中各顶点活动所需要的时间,那么就可以将AOV网转换为AOE网

(比较简单的方法是:

  • 将AOV网中的每个顶点都拆成两个顶点,分别表示活动的起点和终点,
  • 两个顶点通过有向边连接,该有向边表示原顶点的活动,边权为需要的时间
  • 原AOV网中的边全部视为空活动,边权为0(因为AOE结点表示已完成)

 

  

总花费时间=到达终点的多条路径中花费时间最长的那条

关键路径(要达到最短花费时间,不能拖延的路径):AOE网中的最长路径

二、最长路径

最长路径

1.对一个没有从源点可达的正环的图,

可以把所有边的边权乘-1,然后用Bellman-Ford算法或SPFA算法求最短路径长度,

将所得结果取反即可。(不能使用Dijkstra,因为可能出现负权边)

2.对一个有正环的图,最长路径是不存在的。(因为一条边,一个顶点可以访问多次)

但是最长简单路径是存在的,只是无法通过Bellman-Ford等算法求解。

原因是最长路径问题是NP-Hard问题(没有多项式时间复杂度算法问题) 

三、关键路径

给出比上面求解更快的方法

记录:

   活动最早开始时间:e[r]; 活动最迟开始时间:l[r]

   关键路径:e[r]==l[r],最早开始时间=最迟开始时间(不能拖延)

->事件最早发生时间:ve[i];事件最迟发生时间:vl[i]

1.活动ar只要在事件vi最早发生时马上开始,就可以使得活动ar的的开始时间最早

e[r]=ve[i]

2.如果l[r]是活动ar的最迟发生时间,那么l[r]+length[r]就是事件vj的最迟发生时间

l[r]=vl[j]-length[r]  

将问题由求e[],l[]两个数组(活动)

转化为了

求ve[],vl[]两个数组(事件)

 

如左图,求ve[j],必须先知道其所有前驱结点ve[i1]-ve[ik]的值

如右图,求vl[i], 必须先知道其所有后继结点vl[j1]-vl[jk]的值

1.为了保证访问ve[j]时,其所有前驱结点都访问完毕——使用拓扑排序

因为通过后继ve[j]找其所有前驱比较麻烦,因此可以直接每次更新ve[i]所有后继结点

2.为了保证访问vl[i] 时,其所有后继结点都访问完毕——使用逆拓扑序列

访问一遍后继结点,通过每个后继结点更新前驱

//拓扑排序,顺便求ve[]
stack<int> topOrder;
bool topologicalSort(){
    queue<int> q;
    for(int i=0;i<nv;i++){
        if(inDegree[i]==0){
            q.push(i);
        }
    }

    while(!q.empty()){
        int u=q.front();
        q.pop();
        topOrder.push(u);//将u加入拓扑序列
        for(int i=0;i<Adj[u].size();i++){
            int v=Adj[u][i].v;  //u的i号后继结点编号为v
            inDegree[v]--;
            if(inDegree[v]==0){
                q.push(v);
            }
            //用ve[u]来更新u的所有后继结点v
            if(ve[u]+Adj[u][i].weight>ve[v]){
                ve[v]=ve[u]+Adj[u][i].weight;
            }
        }
    }
    if(topOrder.size()==nv) return true;
    else                    return false; 
}
//逆拓扑序列,并求vl
//逆拓扑序列:颠倒拓扑序列,只需要将topOrder逐个出栈即可
bool reverse_topologicalSort(){
    fill(vl,vl+nv,ve[nv-1]);//vl初始化为终点的最早发生时间ve[nv-1](假设终点为nv-1)
    //因为终点的ve一定是最大的,且ve[终点]=vl[终点]

    while(!topOrder.empty()){
        int u=topOrder.top();
        topOrder.pop();

        for(int i=0;i<Adj[u].size();i++){
            int v=Adj[u][i].v;
            //用u所有后继结点v的vl值来更新前驱结点vl[u]
            if(vl[v]-Adj[u][i].weight<vl[u]){
                vl[u]=vl[v]-Adj[u][i].weight;
            }
        }
    }
}  

 总结找关键路径方法:

 

 

int CriticalPath(){
    //1.1:计算ve数组
    fill(ve,ve+nv,0);//ve数组初始化
    if(topologicalSort()==false){
        return -1;//不是有向无环图,返回-1
    }
    //1.2:计算vl数组
    fill(vl,vl+nv,ve[nv-1]);//vl数组初始化
    while(!topOrder.empty()){
        int u=topOrder.top();
        topOrder.pop();
        for(int i=0;i<Adj[u].size();i++){
            int v=Adj[u][i].v;
            //用u所有后继结点v的vl值来更新vl[u]
            if(vl[v]-Adj[u][i].weight<vl[u]){
                vl[u]=vl[v]-Adj[u][i].weight;
            }
        }
    }

    //2:遍历所有边,计算e[]和l[]数组
    for(int u=0;u<nv;u++){
        for(int i=0;i<Adj[u].size();i++){
            int v=Adj[u][i].v,w=Adj[u][i].weight;
            //活动的最早开始时间e,最迟开始时间l
            int e=ve[u],l=vl[v]-w;
            if(e==l){
                //输出关键活动
                printf("%d->%d\n",u,v);
            }
        }
    }
    return ve[nv-1];//返回关键路径长度
}

//邻接表存的就是边

1.如果需要存储活动的最早开始时间e和最迟开始时间l,则只需在结构体Node中添加域e和l

2.如果事先不知道终点(汇点)编号,如何初始化vl数组的值呢?

ve数组指事件的最早开始时间,汇点的最早开始时间一定是最大的,因此取ve数组最大值即可。

3.如果需要存储关键路径?

新建一个邻接表,将关键路径的边存进去,最后按需要遍历所有边(DFS,BFS)即可

终点的最早开始时间ve[终点]=最迟开始时间vl[终点]

(最早)工期

  • 单一汇点时,汇点的最早开始时间ve[终点]
  • 多汇点时,最大的ve[]
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值