图论 —— AOE 网与关键路径

【AOE 网】

在表示一个工程时,用顶点表示事件,用弧表示活动,权值表示活动的持续时间,这样的有向图即为 AOE 网。

其有两个性质:

  • 在顶点表示事件发生之后,从该顶点出发的有向弧所表示的活动才能开始。
  • 在进入某个顶点的有向弧所表示的活动完成之后,该顶点表示的事件才能发生。

对于一个工程来说,只有一个开始状态和一个结束状态,因此在 AOE 网中,只有一个入度为 0 的点表示工程的开始,即源点,也只有一个出度为 0 的点表示工程的结束,即汇点

AOE 网常用于进行工程管理,其解决的主要问题是:

  • 计算完成整个工程的最短工期
  • 确定关键路径,以找出哪些活动是影响工程进度的关键

【关键路径】

在 AOE 网上,从源点到汇点的具最大路径长度(该路径上行各活动持续时间的和)的路径称为关键路径,关键路径上的活动称为关键活动

要找出关键路径,就要找出关键活动,即不按期完成就会影响整个工程的活动

1.事件 vk 的最早发生时间 ve[k]

在保证整个工程完成的前提下,事件最早的开始时间称为事件 vk 的最早发生时间,记作:ve[k]

ve[k] 的大小实际上为从源点开始到顶点 vk 的最大路径长度,那么,求解 ve[k] 可以从源点 ve[1]=0 开始,按照拓扑排序规则递推得到

即有:ve(k)=max\{ve(j)+len<v_j,v_k>\}(<v_j,v_k>\in p[k])

其中,len<vj,vk> 是弧 <vj,vk> 上的权值,p[k] 是所有到达 vk 的有向边的集合。

2.事件 vk 的最晚发生时间 vl[k]

在保证整个工程完成的前提下,事件最迟的开始时间称为事件 vk 的最晚发生时间,记作 vl[k]

求解 vl[k] 可以从汇点 vl[n]=ve[n] 开始,向源点递推得到,即有:vl[k]=min\{ vl[j]-len<v_k,v_j> \}(<v_k,v_j>\in s[k])

其中,len<vk,vj> 是弧 <vk,vj> 上的权值,s[k] 是所有从 vk 发出的有向边的集合。

3.活动 ai 的最早开始时间 ee[i]

在保证工程顺利完成的基础上,活动 ai 最早的必须开始时间称为活动 ai 的最早开始时间,记作:ee[i]

若活动 ai 是由弧 <vk,vj> 表示,根据 AOE 网的性质,只有事件 vk 发生后,活动 ai 才能开始

那么也就是说,活动 ai 的最早开始时间等于时间 vk 的最早发生时间,即有:ee[i]=ve[k]

4.活动 ai 的最晚开始时间 el[i]

在不推迟整个工程完成时间的基础上,活动 ai 最迟的必须开始时间称为活动 ai 的最晚开始时间,记作:el[i]

若活动 ai 是由弧 <vk,vj> 表示,则 ai 的最晚开始时间要保证时间 vj 的最迟发生时间不拖后,即有:el[i]=vl[j]-len<vk,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)(求关键路径长度)点击这里
  • 16
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值