AOE关键路径算法及代码(C++版)

起因
我上网搜了很久,没有看见别人写的AOE算法,要么就是用C写的,看上去很复杂,也没搞清楚他们这样写的意义。又看到了一个不是用C写的,但是我看不懂他写的什么。最后决定自己实现一下。由于没有图,所以可能需要先理解AOE,再看实现过程。
PS:如果觉得对你有用的话请点个赞或者评论,给我一个鼓励。或者对Code觉得有意见的话,可以把问题写在下面。


应用
AOE关键路径算法的应用:能够合理安排工程顺序。


工程图

例图

PS:上图来自于//www.greatytc.com/p/1857ed4d8128。里面有过程推导(写的也一般)。


存储结构
1.AOE:Activity on Edge,活动在边上。
2.在图的存储中,顶点表示状态,边表示活动
3.我们的目的,是从起始状态-->终止状态。


目的
获得每一个活动的开始时间的范围
当该活动开始时间范围只有一个点:最早开始时间==最晚开始时间-->关键活动
从开始到结束,存在一条路径,路径上所有的活动都是关键活动,该路径为关键路径


前置知识:拓扑排序,详情请见我另一篇博客(还没写)


核心思想
1.状态的最早实现时间。用vEarlist 存储
前置状态结束以后,才可以到达这个状态

  • -->vEarlist[v] = max(vEarlist[v], vEarlist[u] + G[u][v])

2.状态的最迟实现时间。用vLatest存储
后面那个状态实现的最迟时间-活动时间(对时间提出要求,至少得在这个时间完成)

  • -->vLatest[v] = min(vLatest[v], vLatest[u] - G[v][u])

3.活动的最早实现时间。用eEarlist存储
前面那个状态到达了,活动即可开始:前面状态的最早开始时间

  • -->eEarlist[i] = vEarlist[活动前状态]

4.活动的最迟实现时间。用eLatest存储
后面那个状态到达的最迟时间-活动时间

  • -->eLatest[i] = vLatest[活动后状态] - i活动的时间

代码中将活动的信息全部打包。
eEarlist,eLatest,花费时间expd,状态前后startV和endV


算法
1.先进行拓扑排序,获得拓扑序列。
2.依据拓扑序列,更新顶点状态:顶点最早开始时间
3.找到最终结束点的最早开始时间,更新vLatest的最终点
4.对拓扑排序的逆序,进行操作,找到其相邻边,更新vLatest
5.对所有活动 更新其最早开始时间-->顶点最早开始时间 更新其最迟开始时间-->后顶点最迟开始时间-时长 更新可休息时间-->最迟-最早 如果可休息时间为0-->pre[后].push_back(前)
6.从结尾开始进行DFS,获得关键路径

输入格式N(顶点数)M(边数)源点汇点
M行,活动开始状态(顶点),活动结束状态(顶点),活动进行时间。

输出格式

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
struct node{
    int eEarlist;
    int eLatest;
    int eDiff;
    int expd;
    int startV, endV;
};
void DFS(int u);
int G[510][510];
const int MAX = 0x7FFFFFFF;
vector<int> vEarlist, vLatest;//状态最早开始时间和最迟开始时间
vector<node> actvt;//活动记录
vector<int> topoSeq;//拓扑排序的序列
vector<int> inDegree;//入度(配合拓扑排序使用)
vector<vector<int> > path;//关键路径(可能不止一条)
vector<int> tempPath;//DFS作临时路径
vector<vector<int> > pre;//状态的父状态(通过某条活动a->b,则pre[b]=a(其中一条))
int N, M, startV, endV;
int main()
{
    fill(G[0], G[0]+510*510, MAX);
    cin >> N >> M >> startV >>endV;
    vEarlist.resize(N); vLatest.resize(N);
    actvt.resize(M);
    inDegree.resize(N);
    fill(inDegree.begin(), inDegree.end(), 0);
    for(int i=0; i<M; i++){
        int u, v, expd;
        cin >> u >> v >> expd;
        G[u][v] = expd;
        actvt[i].expd = expd; actvt[i].startV = u; actvt[i].endV = v;
        inDegree[v]++;
    }
    //1.先进行拓扑排序,获得拓扑序列
    queue<int> q;
    q.push(startV);
    while(!q.empty()){
        int u = q.front();
        q.pop();
        for(int i=0; i<N; i++){
            if(G[u][i] != MAX){//存在这条路
                inDegree[i]--;
                if(inDegree[i]==0){
                    q.push(i);
                }
            }
        }
        topoSeq.push_back(u);
    }
    //2.依据拓扑序列,更新顶点状态:顶点最早开始时间
    fill(vEarlist.begin(), vEarlist.end(), 0);
    for(int i=0; i<topoSeq.size(); i++){
        int u = topoSeq[i];
        for(int j=0; j<N; j++){
            if(G[u][j] != MAX && vEarlist[u] + G[u][j] > vEarlist[j]){
                vEarlist[j] = vEarlist[u] + G[u][j];
            }   
        }
    }
    //3.找到最终结束点的最早开始时间,更新vLatest的最终点
    fill(vLatest.begin(), vLatest.end(), vEarlist[endV]);
    //4.对拓扑排序的逆序,进行操作,找到其相邻边,更新vLatest
    reverse(topoSeq.begin(), topoSeq.end());//直接改就行,因为用不到了
    for(int i=0; i<N; i++){
        int u = topoSeq[i];//此时为逆序,所以u在后
        for(int j=0; j<N; j++){
            if(G[j][u] != MAX && vLatest[u] - G[j][u] < vLatest[j]){//有到达u的点
                vLatest[j] = vLatest[u] - G[j][u];
            }
        }
    }
    //5.对所有活动,更新其最早开始时间-->顶点最早开始时间
    //              更新其最迟开始时间-->后顶点最迟开始时间-时长
    //              更新可休息时间-->最迟-最早
    //              如果可休息时间为0-->pre[后].push_back(前)
    //              
    pre.resize(M);
    for(int i=0; i<M; i++){
        actvt[i].eEarlist = vEarlist[actvt[i].startV];
        actvt[i].eLatest  = vLatest[actvt[i].endV]     -     actvt[i].expd;
        actvt[i].eDiff    = actvt[i].eLatest           -     actvt[i].eEarlist;
        if(actvt[i].eDiff == 0){
            pre[actvt[i].endV].push_back(actvt[i].startV);
        }
    }     
    //6.对结尾进行DFS,获得关键路径
    // DFS(endV);
    // path存储路径
    DFS(endV);
    for(int i=0; i<path.size(); i++){
        for(int j=path[i].size()-1; j>=0; j--){
            if(j!=path[i].size()-1)cout << "-->";
            cout << path[i][j];
        }
        cout << endl;
    }
}
void DFS(int u)
{
    tempPath.push_back(u);
    if(u == startV){
        path.push_back(tempPath);
        tempPath.pop_back();
        return;
    }
    for(int i=0; i<pre[u].size(); i++){
        DFS(pre[u][i]);
    }
    tempPath.pop_back();
}

`

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容