图论 —— AOE 网与关键路径
【AOE 网】
在表示一个工程时,用顶点表示事件,用弧表示活动,权值表示活动的持续时间,这样的有向图即为 AOE 网。
其有两个性质:
- 在顶点表示事件发生之后,从该顶点出发的有向弧所表示的活动才能开始。
- 在进入某个顶点的有向弧所表示的活动完成之后,该顶点表示的事件才能发生。
对于一个工程来说,只有一个开始状态和一个结束状态,因此在 AOE 网中,只有一个入度为 0 的点表示工程的开始,即源点,也只有一个出度为 0 的点表示工程的结束,即汇点。
AOE 网常用于进行工程管理,其解决的主要问题是:
- 计算完成整个工程的最短工期
- 确定关键路径,以找出哪些活动是影响工程进度的关键
【关键路径】
在 AOE 网上,从源点到汇点的具最大路径长度(该路径上行各活动持续时间的和)的路径称为关键路径,关键路径上的活动称为关键活动。
要找出关键路径,就要找出关键活动,即不按期完成就会影响整个工程的活动。
1.事件 vk 的最早发生时间 ve[k]
在保证整个工程完成的前提下,事件最早的开始时间称为事件 vk 的最早发生时间,记作:ve[k]
ve[k] 的大小实际上为从源点开始到顶点 vk 的最大路径长度,那么,求解 ve[k] 可以从源点 ve[1]=0 开始,按照拓扑排序规则递推得到
即有:
其中,len<vj,vk> 是弧 <vj,vk> 上的权值,p[k] 是所有到达 vk 的有向边的集合。
2.事件 vk 的最晚发生时间 vl[k]
在保证整个工程完成的前提下,事件最迟的开始时间称为事件 vk 的最晚发生时间,记作 vl[k]
求解 vl[k] 可以从汇点 vl[n]=ve[n] 开始,向源点递推得到,即有:
其中,len<vk,vj> 是弧 <vk,vj> 上的权值,s[k] 是所有从 vk 发出的有向边的集合。
3.活动 ai 的最早开始时间 ee[i]
在保证工程顺利完成的基础上,活动 ai 最早的必须开始时间称为活动 ai 的最早开始时间,记作:ee[i]
若活动 ai 是由弧 <vk,vj> 表示,根据 AOE 网的性质,只有事件 vk 发生后,活动 ai 才能开始
那么也就是说,活动 ai 的最早开始时间等于时间 vk 的最早发生时间,即有:
4.活动 ai 的最晚开始时间 el[i]
在不推迟整个工程完成时间的基础上,活动 ai 最迟的必须开始时间称为活动 ai 的最晚开始时间,记作:el[i]
若活动 ai 是由弧 <vk,vj> 表示,则 ai 的最晚开始时间要保证时间 vj 的最迟发生时间不拖后,即有:
其中,len<vk,vj> 是弧 <vk,vj> 上的权值
5.活动 ai 的松弛时间 el[i]-ee[i]
活动 ai 的最晚开始时间与最早开始时间的差值称为活动 ai 的松弛时间,记作:el[i]-ee[i]
当 el[i]=ee[i] 时,对应的活动 ai 称为关键活动,那些 el[i]>ee[i] 的活动则不是关键活动。
在关键活动确定后,关键活动所在的路径就是关键路径。
【实现】
1.输出关键路径
根据关键路径的定义,依次求出 ve、vl、ee、el,然后比较 ee、el 进行输出
int n,m;
int G[N][N];//邻接矩阵
int in[N];//入度
int ve[N];//事件vk的最早发生时间
int vl[N];//事件vk的最晚发生时间
int ee[N];//活动ai的最早开始时间
int el[N];//活动ai的最晚开始时间
int Stack[N];//栈
struct Edge {
int x,y;
int dis;
Edge(){}
Edge(int x,int y,int dis):x(x),y(y),dis(dis){}
}edge[N];
bool vis[N];
void getVe(){//求ve
int cnt=0;
for(int i=1;i<=n;i++){
int k=-1;
for(int j=1;j<=n;j++){
if(in[j]==0){
Stack[++cnt]=j;
k=j;
in[j]=-1;
break;
}
}
for(int j=1;j<=n;j++){
if(G[k][j]!=INF){
ve[j]=max(ve[j],ve[k]+G[k][j]);
in[j]--;
}
}
}
}
void getVl(){//求vl
memset(vl,INF,sizeof(vl));
vl[Stack[n]]=ve[Stack[n]];
for(int i=n;i>=1;i--){
for(int j=1;j<=n;j++){
if(G[Stack[i]][j]!=INF) {
vl[Stack[i]]=min(vl[j]-G[Stack[i]][j],vl[Stack[i]]);
}
}
}
}
void getEe(){//求ee
for(int i=1;i<=m;i++)
ee[i]=ve[edge[i].x];
}
void getEl(){//求el
for(int i=1;i<=m;i++)
el[i]=vl[edge[i].y]-edge[i].dis;
}
void printEdge(){//以边输出
for(int i=1;i<=m;i++)
if(ee[i]==el[i])
printf("<%d,%d>:%d\n", edge[i].x, edge[i].y, edge[i].dis);
}
void printNode(){//以点输出
priority_queue<int,vector<int>,greater<int> > Q;
memset(vis,false,sizeof(vis));
for(int i=1;i<=m;i++){
if(ee[i]==el[i]){
int x=edge[i].x;
int y=edge[i].y;
if(!vis[x]){
Q.push(x);
vis[x]=true;
}
if(!vis[y]){
Q.push(y);
vis[y]=true;
}
}
}
while(!Q.empty()){
int temp=Q.top();
Q.pop();
printf("v%d ",temp);
}
}
int main() {
memset(G,INF,sizeof(G));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y,dis;
scanf("%d%d%d",&x,&y,&dis);
edge[i].x=x;
edge[i].y=y;
edge[i].dis=dis;
G[x][y]=dis;
in[y]++;
}
getVe();
getVl();
getEe();
getEl();
printf("以边输出:\n");
printEdge();
printf("以点输出:\n");
printNode();
return 0;
}
2.求关键路径长度
由于关键路径是具有最大路径长度的路径,因此直接求有向图的最长路即为关键路径的长度
struct Node{
int to,dis;
Node(){}
Node(int to,int dis):to(to),dis(dis){}
};
int n,m;
int in[N];
vector<Node>G[N];
int dis[N];
void getPath() {
queue<int> Q;
for(int i=0;i<n;i++){
if(in[i]==0){
Q.push(i);
dis[i]++;
}
}
while(!Q.empty()){
int x=Q.front();
Q.pop();
for(int i=0;i<G[x].size();i++){
int y=G[x][i].to;
int diss=G[x][i].dis;
dis[y]=max(dis[y],dis[x]+diss);
if (--in[y]==0)
Q.push(y);
}
}
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y,dis;
scanf("%d%d%d",&x,&y,&dis);
G[x].push_back(Node(y,dis));
in[y]++;
}
getPath();
int res=-INF;
for(int i=0;i<n;i++)
res=max(res,dis[i]);
printf("%d\n",res);
return 0;
}
【例题】
- Instrction Arrangement(HDU-4109)(求关键路径长度):点击这里
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通