拓扑排序(删边/DFS)➕AOE网关键路径

这篇博客的缘起是女鹅问的一道AOE网的题,然而上一波数据结构并没有全看完就莫名搁置了,当时看图的后半部分算法很多,想着实现一下,一直静不下心搞,就无限期搁置了(叹气,戴起老花镜开始研究= =

题目

原题

牛客网上此题的讨论
在这个讨论区看到了五花八门的答案,陷入沉思,决定自己实现一下AOE网求关键活动。

动手实现

  • 首先看看这个算法详细的流程


    维基百科对AOE网的介绍
  • 拓扑排序有删边、dfs两种实现,下面实现的是删边法(🚩dfs有空再补吧
    严格按照算法的步骤实现如下
#include <cstdio>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <algorithm>

#define MAXN 1001 // max num of vertices
#define INF 0x3f3f3f3f
using namespace std;
vector<pair<int, int >> graph[MAXN], rgraph[MAXN];
int nv, ne; // vertex 1 - nv  edge from----weight----to
int in_degree[MAXN] = {0}; // out_degree[i] = graph[i].size();
queue<int> mq; // TopologicalSort
stack<int> ms; //reverse TopologicalSort
int ve[MAXN], vl[MAXN];
map<pair<int, int>, pair<int, int>> ee_el;

// 合法输入为 无重边的有向无环图
void bulidAOE() {
    scanf("%d%d", &nv, &ne);
    int from, to, weight;
    for (int i = 0; i < ne; ++i) {
        scanf("%d%d%d", &from, &to, &weight);
        graph[from].emplace_back(to, weight);
        rgraph[to].emplace_back(from, weight);
        in_degree[to]++;
        ee_el[make_pair(from, to)] = make_pair(0, -weight);
    }
    puts("Activity On Edge Network has been built.");
    for (int i = 1; i <= nv; ++i) {
        for (auto item:graph[i]) {
            printf("V%d ---- %d ---> V%d\n", i, item.second, item.first);
        }
    }
};

/*
 * 删边法
 * queue 保存拓扑排序结果
 * 循环:选择入度为0的结点,将编号入队,删去与之相连的边(该点发出的边, 将指向的结点入度-1即可)
 * 若过程中某步找不到入度为0的结点,return false
 */
bool topologicalSort() { // if not DAG return false
    printf("TopologicalSort\n");
    bool done[MAXN] = {false};
    for (int i = 0; i < nv; ++i) {
        int curr = -1;
        for (int j = 1; j <= nv; ++j) {
            if (!done[j] && in_degree[j] == 0) {
                curr = j;
                break;
            }
        }
        if (curr == -1) {
            puts("Error! Not a DAG!");
            return false;
        }
        mq.push(curr);
        done[curr] = true;
        printf("%d - ", curr);
        for (auto item:graph[curr]) {
            in_degree[item.first]--;
        }
    }
    puts("");
    return true;
}

/*
 * 此处需要知道各结点的入边,因此最初存边的时候,不仅要存from-to,还要存to-from
 * 需要取max min 注意初始化
 * 若用邻接矩阵实现,直接遍历一列就知道有无入边了
 * 计算结点事件最早时间 ve【i】
 *      按拓扑序(queue出队)的次序,计算事件最早时间
 *      顺便将出队的结点序号入栈,以获得逆拓扑序列
 * 计算结点时间最晚时间 vl【i】
 *      按逆拓扑序(stack出栈)的次序,计算事件最晚开始时间
 *
 * 活动记作 pair<from,to>    map<pair<int, int>, pair<int, int>> ee_el;
 * 计算活动最早开始时间 ee
 * 计算活动最晚开始时间 el
 */
void calcEarlyLate() {
    fill(ve + 1, ve + nv + 1, 0);
    fill(vl + 1, vl + nv + 1, INF);
    //  calc ve
    int curr;
    if (!mq.empty()) {
        curr = mq.front();
        mq.pop();
        ms.push(curr);
        ve[curr] = 0;
    }
    while (!mq.empty()) {
        curr = mq.front();
        ms.push(curr);
        mq.pop();
        for (auto item:rgraph[curr]) {
            ve[curr] = max(ve[item.first] + item.second, ve[curr]);
        }
    }
    // calc vl
    vl[curr] = ve[curr]; // curr = ms.top()
    ms.pop();
    while (!ms.empty()) {
        curr = ms.top();
        ms.pop();
        for (auto item:graph[curr]) {
            vl[curr] = min(vl[item.first] - item.second, vl[curr]);
        }
    }
    printf("No        ve        vl\n");
    for (int i = 1; i <= nv; ++i) {
        printf("%d%10d%10d\n", i, ve[i], vl[i]);
    }
    printf("from      to        ee        el        \n");
    // calc ee and el
    for (auto &item : ee_el) {
        item.second.first = ve[item.first.first];// ee[j,k] = ve[j]
        item.second.second += vl[item.first.second]; // el[j,k] = vl[k] - weight<j-k>
        printf("%d%10d%10d%10d", item.first.first, item.first.second, item.second.first, item.second.second);
        puts(item.second.first == item.second.second ? "        Critical" : "");
    }
}

int main() {
    bulidAOE();
    if (topologicalSort()) {
        calcEarlyLate();
    }
    return 0;
}
  • 把上面那道题目丢进去跑一下:


    运行结果

    ⚠️ 于是发现牛客讨论区列出ve,vl,ee,el表格的朋友答案似乎不大靠谱???
    似乎没一个列出正确的结果???
    那么这道题到底怎么才能得到正解呢???

正解

回到wikipedia上对整体工期的解释,它应当是源点到汇点最长路径的长度,所以要缩短工期,就是要减少最长路径(关键路径)的长度

原题-图片

源点到汇点的路径&总长度:

  • 1 —— 2 —— 4 —— 6 18
  • 1 —— 2 —— 5 —— 6 18
  • 1 —— 3 —— 5 —— 6 27
  • 1 —— 3 —— 2 —— 4 —— 6 27
  • 1 —— 3 —— 2 —— 5 —— 6 27
    后面三条为关键路径,只需找出能同时缩短他们三个的边即可。
    其实答案有很多种,因为除了1——2都是关键活动 = =
    缩短1——3可以缩短全部三条,缩短3——2可以缩短后两条,缩短5——6可以缩短第一和第三条(省略blabla)
    看选项,发现只有C满足同时缩短三条关键路径,即缩短了最长路径 #

至此,终于清爽的八达鸟~

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