关键路径求解

关键路径(Critical Path)是项目管理中的一个概念,它指的是项目中最长的任务序列,决定了完成项目所需的最短时间。关键路径上的任务称为关键任务,它们对项目的完成时间有直接影响。

我们需要先了解以下几个概念:

AOE网(Activity On Edge Network):是一种有向无环图,用于表示项目中的任务和任务之间的依赖关系。在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销(如完成活动需要的时间)。

(1)在AOE网中仅有一个入度为0的顶点,称为开始顶点(源点),它表示整个工程的开始;

(2)也仅有一个出度为0的顶点,称为结束顶点(汇点),它表示整个工程的结束。

(边的第一个标注是活动id,第二个是权值)

并且遵循以下规则

1.只有到达某个事件后以该事件为起点的活动才能开始;

2.只有以某个事件为终点的所有活动结束后,该事件才能发生(要消除所有指向该事件的边)。

计算关键路径通常需要用到以下概念

事件的最早发生时间ve,事件最迟发生时间vl,活动的最早发生时间ae,活动的最迟发生时间al,活动冗余时间d=al-ae。如果某活动的d=0,则该活动是关键活动。

关键路径求解步骤

1.从源点(开始项目)开始,按拓扑顺序计算每个事件的最早发生时间ve;

2.从汇点(结束项目)开始,按拓扑逆序计算每个事件的最迟发生时间vl;

3.计算每个活动的最早发生时间ae和最迟发生时间al;

4.判断每个活动是否为关键活动,即判断d是否为0。

代码实现:

#include<bits/stdc++.h>

using namespace std;

struct Activity {
    int id;
    int u, v, w;//活动的起点和终点和权值
    int ve; // 活动最早开始时间
    int vl; // 活动最迟开始时间
    int d; // 活动冗余时间
};

struct Event {
    int id;
    int ve; // 事件最早发生时间
    int vl; // 事件最迟发生时间
    vector<Activity> inEdges; // 以这个事件开始的活动
};

int n, m; // n表示顶点数,m表示边数
vector<Event> events; // 事件集合

void init() {
    cin >> n >> m;
    // 初始化事件集合
    for(int i = 0; i < n; i++) {
        events.push_back(Event{i, INT_MIN, INT_MAX, vector<Activity>()});
    }
    // 初始化活动集合
    for(int i = 0; i < m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        events[u].inEdges.push_back(Activity{i, u, v, w, 0, INT_MAX, -1});
    }
}

void dfs(int v, int currTime) {
    events[v].ve = max(events[v].ve, currTime);//更新事件的最早发生时间
    if(events[v].inEdges.size() == 0) return; // 结束活动
    for(auto& activity : events[v].inEdges) {
        dfs(activity.v, currTime + activity.w); // 递归计算后续活动的最早发生时间
    }
}

void getStartEvent(int v, queue<int>& q) {
    for(Event event : events) {
        for(Activity edge : event.inEdges) {
            if(edge.v == v) {
                q.push(edge.u);
            }
        }
    }
}

//bfs更新事件的最迟发生时间
void getVL(int v) {
    queue<int> q;
    q.push(v);
    while(!q.empty()) {
        int currV = q.front();
        q.pop();
        queue<int> tempq;
        getStartEvent(currV, tempq);
        while(!tempq.empty()) {
            int Min = INT_MAX;
            for(Activity &act : events[tempq.front()].inEdges) {
                if(act.v == currV) {
                    act.vl = events[currV].vl - act.w;
                    Min = min(Min, act.vl);
                }
            }
            events[tempq.front()].vl = Min;
            q.push(tempq.front());
            tempq.pop();
        }
    }
}

int main() {
    //初始化一个AOE网
    init();
    //通过dfs来计算每个事件的最早发生时间
    dfs(0, 0); //从源点开始
    //更新活动的最早发生时间
    for(int i = 0; i < n; i++) {
        for(auto& activity : events[i].inEdges) {
            activity.ve = events[i].ve;
        }
    }
    events[n - 1].vl = events[n - 1].ve; //更新汇点的最迟发生时间
    //通过拓扑排序来计算每个事件和活动的最迟发生时间
    getVL(n-1);
    //计算每个活动的冗余时间,输出关键路径
    int spend = 0;
    for(int i = 0; i < n; i++) {
        for(auto& activity : events[i].inEdges) {
            activity.d = activity.vl - activity.ve;
            if(activity.d == 0) {
                spend += activity.w;
                cout << "活动id:" << activity.id << " 活动起始事件id:" << activity.u << " 活动指向事件id:" << activity.v << endl;
            }
        }
    }
    cout << "关键路径长度为:" << spend << endl;
    return 0;
}

/*
4 4
0 2 2
0 1 1
1 2 3
2 3 2
*/

//图例就是这组数据
/*
6 8
0 1 3
0 2 2
1 3 2
1 4 3
2 3 5
2 5 3
3 5 2
4 5 1
*/

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值