关键路径

本文介绍了一种求解关键路径的方法,通过拓扑排序确定任务的最早开始时间和最晚完成时间,进而找出关键路径。提供了完整的C++代码实现,包括正向和逆向拓扑排序过程。

先来说下关键路径的求解思路,引用于百度百科。

1.从开始顶点 v出发,令 ve(1)=0,按拓扑有序序列求其余各顶点的可能最早发生时间。

Ve(k)=max{ve(j)+dut(<j,k>)} , j ∈ T 。其中T是以顶点vk为尾的所有弧的头顶点的集合(2 ≤ k ≤ n)。

如果得到的拓朴有序序列中顶点的个数小于网中顶点个数n,则说明网中有环,不能求出关键路径,算法结束。

2.从完成顶点 出发,令 ,按逆拓扑有序求其余各顶点的允许的最晚发生时间:vl(j)=min{vl(k)-dut(<j,k>)} ,k ∈ S 。其中 S 是以顶点vj是头的所有弧的尾顶点集合(1 ≤ j ≤ n-1)。

3.求每一项活动ai(1 ≤ i ≤ m)的最早开始时间e(i)=ve(j),最晚开始时间l(i)=vl(k)-dut(<j,k>) 。若某条弧满足 e(i)=l(i) ,则它是关键活动。

#include <cstdio>
#include <vector>
#include <string>
#include <cstring>
#include <algorithm>
#include <queue>
#include <iostream>

using namespace std;

const int maxn=1e2+2;
const int  INF =0x3f3f3f3f;
vector<int> Gz[maxn],Gn[maxn];
int Mao[maxn][maxn],idx[maxn][maxn];
int indx[maxn],outdx[maxn],vz[maxn],vn[maxn];
int n,m,x,y,val,ans;

struct P
{
    int u,v,id;//id用来标记顺序
    bool operator<(const P & a)const//这个只是按照题目要求输出
    {
        if(u<a.u || (u==a.u && id>a.id)) return true;
        else return false;
    }
}path[maxn*maxn];

void inition()
{
    memset(idx,0,sizeof(idx));
    memset(indx,0,sizeof(indx));
    memset(outdx,0,sizeof(outdx));
    memset(Mao,0,sizeof(Mao));
    memset(vz,0,sizeof(vz));
    memset(vn,INF,sizeof(vn));//最晚发生时间要初始化最大
    for(int i=0; i<n; i++) Gz[i].clear();
    for(int i=0; i<n; i++) Gn[i].clear();
}

int topoz()
{
    int sum=0;
    queue<int> q;
    for(int i=1; i<=n; i++)//正向拓扑时把所有入度为零的加入队列,即多个起点都加入队列
        if(!indx[i])
            q.push(i);
    while(!q.empty())
    {
        sum++;
        int u=q.front();
        q.pop();
        for(int i=0; i<Gz[u].size(); i++)//just 拓扑,但是要求出最早发生的时间,并且注意删除边后要判断这个点是否成为新的起点,是就加入队列
        {
            int v=Gz[u][i];
            vz[v]=max(vz[v],vz[u]+Mao[u][v]);
            indx[v]--;
            if(!indx[v]) q.push(v);
        }
    }
    return sum;
}

void topon()
{
    queue<int> q;
    for(int i=1; i<=n; i++)//逆序的拓扑和正序道理一样,只是要求解最晚发生时间
    {
        if(!outdx[i])
        {
            q.push(i);
            vn[i]=ans;
        }
    }
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=0; i<Gn[u].size(); i++)
        {
            int v=Gn[u][i];
            vn[v]=min(vn[v],vn[u]-Mao[v][u]);
            outdx[v]--;
            if(!outdx[v]) q.push(v);
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    inition();
    for(int i=1; i<=m; i++)
    {
        scanf("%d%d%d",&x,&y,&val);
        Mao[x][y]=Mao[y][x]=val;//这里用数组Mao存储边的权值,用vector存储边,只是为了减少不必要的循环
        indx[y]++;
        outdx[x]++;//记录出度入度
        Gz[x].push_back(y);
        Gn[y].push_back(x);
        idx[x][y]=idx[y][x]=i;//这个地方记录边的顺序
    }
    if(topoz()!=n) printf("0\n");//如果无法拓扑则说明失败
    else
    {
        ans=0;
        for(int i=1; i<=n; i++)
            if(!outdx[i])
                ans=max(ans,vz[i]);//求解出关键活动的权值
        printf("%d\n",ans);
        topon();
        int cnt=0;
        for(int i=1; i<=n; i++)
        {
            if(vz[i]==vn[i])//当ve和vl值相等,表示此处为关键活动
            {
                for(int j=1;j<=n;j++)
                {
                    if(vz[j]==vn[j] && idx[i][j] && Mao[i][j]+vz[i]==vz[j])//idx[i][j]表示这两点是连通的,Mao[i][j]+vz[i]=vz[j]保证输出路径是正序
                    {
                        path[cnt].u=i;
                        path[cnt].v=j;
                        path[cnt].id=idx[i][j];
                        cnt++;
                    }
                }
            }
        }
        sort(path,path+cnt);
        for(int i=0;i<cnt;i++)
            printf("%d->%d\n",path[i].u,path[i].v);
    }
    return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值