关键路径 图论 ——数据结构课程

AOE网(activity on edge network):在带权有向图中,用顶点表示事件,用有向边表示活动。边权表示活动的持续时间。

源点,终点:AOE图中有唯一的源点和终点,表示活动的开始与终结。

关键路径:所有的活动都完成时整个工程所花费的时间,其实也就是源点到终点的最长路径,该最长路径被称为关键路径(可能有多条),关键路径上的活动被称为关键活动。

事件的最早发生时间:ve[k]:根据AOE网的性质,只有进入Vk的所有活动<Vj, Vk>都结束,Vk代表的事件才能发生,而活动<Vj, Vk>的最早结束时间为ve[j]+len<Vj, Vk>。所以,计算Vk的最早发生时间的方法为:ve[k] = max(ve[j] + len<Vj, Vk>)


事件的最迟发生时间:vl[k]:vl[k]是指在不推迟整个工期的前提下,事件Vk允许的最迟发生时间。根据AOE网的性质,只有顶点Vk代表的事件发生,从Vk出发的活动<Vk, Vj>才能开始,而活动<Vk, Vj>的最晚开始时间为min (vl[j] - len<Vk, Vj>)


活动的最早发生时间:ee[i]:ai由有向边<Vk, Vj>,根据AOE网的性质,只有顶点Vk代表的事件发生,活动ai才能开始,即活动ai的最早开始时间等于事件Vk的最早开始时间。


活动的最迟发生时间:el[i]el[i]是指在不推迟真个工期的前提下,活动ai必须开始的最晚时间。若活动ai由有向边<Vk, Vj>表示,则ai的最晚开始时间要保证事件vj的最迟发生时间不拖后。

具体思路为:先对AOE图进行拓扑排序,排序后顺序更新节点最早发生时间找到最长路径,然后逆序更新节点最迟发生时间。

      最后计算边的最早发生时间和最晚发生时间,其差为0的边为关键路径(因为只是最长路径上的边才会出现0)。

      (在邻接表储存过程中要储存逆邻接表,由终点向源点更新最迟发生时间)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<math.h>
#include<queue>
using namespace std;
const int MaxVnum=100;   //顶点数最大值
int indegree[MaxVnum];   //入度数组
int ve[MaxVnum];         //事件vi的最早发生时间
int vl[MaxVnum];         //事件vi的最迟发生时间
int St[MaxVnum] ;
int top=-1;
typedef string VexType;     //顶点的数据类型为字符型
typedef struct AdjNode {    //定义邻接点类型
    int v;                 //邻接点下标
    int weight;            //权值
    struct AdjNode *next;  //指向下一个邻接点指针
} AdjNode;
typedef struct VexNode { //定义顶点类型
    VexType data;           //VexType为顶点的数据类型,根据需要定义
    AdjNode *first;         //指向第一个邻接点指针
} VexNode;
typedef struct { //包含邻接表和逆邻接表的一张图
    VexNode Vex[MaxVnum];          //定义邻接表
    VexNode converse_Vex[MaxVnum]; //定义逆邻接表
    int vexnum, edgenum;           //顶点数,边数
} ALGragh;
//以下为需要用到的函数
int locatevex(ALGragh G, VexType x)            ;//寻找名为 X的节点
void insertedge(ALGragh &G, int i, int j, int w) ;//插入一条边
void CreateALGraph(ALGragh &G)                ; //通过输入数据,创建有向图的邻接表和逆邻接表
bool TopologicalSort(ALGragh G, int topo[])    ;//拓扑排序
void FindInDegree(ALGragh G)                ;//求出各顶点的入度存入数组indegree中
bool CriticalPath(ALGragh G,int topo[])     ;//G为邻接表存储的有向网,输出G的各项关键活动
int main() {
    ALGragh G;
    int *topo=new int[G.vexnum];
    CreateALGraph(G);     //创建有向图的邻接表和逆邻接表
    int *topo=new int[G.vexnum];    
    //printg(G);            //输出邻接表和逆邻接表                 尚未定义
    CriticalPath(G,topo);
    return 0;
}
/*
4 5
0 1 2 3
0 1 4
0 2 3
1 2 3
1 3 6
2 3 4
*/ 




int locatevex(ALGragh G, VexType x) { //寻找节点
    for(int i=0; i<G.vexnum; i++) //查找顶点信息的下标
        if(x==G.Vex[i].data)
            return i;
    return -1;      //没找到
}
void insertedge(ALGragh &G, int i, int j, int w) { //插入一条边
    AdjNode *s1,*s2;
    //创建邻接表结点
    s1=new AdjNode;
    s1->v=j;
    s1->weight=w;
    s1->next=G.Vex[i].first;
    G.Vex[i].first=s1;
    //创建逆邻接表结点
    s2=new AdjNode;
    s2->v=i;
    s2->weight=w;
    s2->next=G.converse_Vex[j].first;
    G.converse_Vex[j].first=s2;
}
void CreateALGraph(ALGragh &G) { //通过输入数据,创建有向图的邻接表和逆邻接表
    int i,j,w;
    VexType u,v;
    cout<<"请输入顶点数和边数:"<<endl;
    cin>>G.vexnum>>G.edgenum;
    cout<<"请输入顶点信息:"<<endl;
    for(i=0; i<G.vexnum; i++) { //输入顶点信息,存入顶点信息数组
        cin>>G.Vex[i].data;
        G.converse_Vex[i].data=G.Vex[i].data;
        G.Vex[i].first=NULL;
        G.converse_Vex[i].first=NULL;
    }
    cout<<"请依次输入每条边的两个顶点及权值u,v,w"<<endl;
    while(G.edgenum--){
        cin>>u>>v>>w;
        i=locatevex(G,u);   //查找顶点u的存储下标
        j=locatevex(G,v);   //查找顶点v的存储下标
        if(i!=-1&&j!=-1)
            insertedge(G,i,j,w);
        else {
            cout<<"输入顶点信息错!请重新输入!"<<endl;
            G.edgenum++;    //本次输入不记入
        }
    }
}
bool TopologicalSort(ALGragh G, int topo[]) { //拓扑排序
    //拓扑排序。有向图G采用邻接表存储结构
    //若G无回路,则生成G的一个拓扑序列topo[]并返回true,否则false
    int i, count;
    FindInDegree(G);  //求出各顶点的入度存入数组indegree[]中
    for(i=0; i<G.vexnum; i++)
        if(!indegree[i]) {//入度为0顶点进栈
            top++;
            St[top]=i;
        } //St.push(i);
    count=0;            //对输出顶点计数,初始为0
    while(top!=-1) { //栈S非空
        i=St[top];    //取栈顶顶点i
        top--;      //栈顶顶点i出栈
        topo[count]=i;    //将i保存在拓扑序列数组topo中
        count++;          //对输出顶点计数

        AdjNode *p=G.Vex[i].first;  //p指向i的第一个邻接点
        while(p) {              //i的所有邻接点入度减1
            int k=p->v;             //k为i的邻接点
            --indegree[k];       //i的每个邻接点的入度减1
            if(indegree[k]==0) { //若入度减为0,则顶点入栈
                top++;
                St[top]=k;
            }
            p=p->next;      //p指向顶点i下一个邻接结点
        }
    }
    if(count<G.vexnum)   //该有向图有回路
        return false;
    else
        return true;
}
void FindInDegree(ALGragh G) { //求出各顶点的入度存入数组indegree中
    int i,count;
    for(i=0; i<G.vexnum; i++) {
        count=0;
        AdjNode *p=G.converse_Vex[i].first;
        if(p) {
            while(p) {
                p=p->next;
                count++;
            }
        }
        indegree[i]=count;
    }
    cout<<"入度数组为:"<<endl;
    for(int i=0; i<G.vexnum; i++) //输出入度数组
        cout<<indegree[i]<<"\t";
    cout<<endl<<endl;
}
bool CriticalPath(ALGragh G,int topo[]) { //G为邻接表存储的有向网,输出G的各项关键活动
    int n,i,k,j,e,l;
    if(TopologicalSort(G,topo)) {
        cout<<"拓扑序列为:"<<endl;
        for(int i=0; i<G.vexnum; i++) //输出拓扑序列
            cout<<topo[i]<<"\t";
        cout<<endl<<endl;
    } else
        cout<<"该图有环,无拓扑序列!"<<endl;
    n=G.vexnum;          //n为顶点个数
    for(i=0; i<n; i++)   //给每个事件的最早发生时间置初值0
        ve[i]=0;
    //按拓扑次序求每个事件的最早发生时间
    for(i=0; i<n; i++) {
        k=topo[i];                    //取得拓扑序列中的顶点序号k

        for(i=0; i<n; i++) {
            k=topo[i];                    //取得拓扑序列中的顶点序号k
            AdjNode *p=G.Vex[k].first;    //p指向k的第一个邻接顶点
            while(p!=NULL) {
                //依次更新k的所有邻接顶点的最早发生时间
                j=p->v;                   //j为邻接顶点的序号
                if(ve[j]<ve[k]+p->weight)   //更新顶点j的最早发生时间ve[j]
                    ve[j]=ve[k]+p->weight;
                p=p->next;                //p指向k的下一个邻接顶点
            }
        }
        for(i=0; i<n; i++)        //给每个事件的最迟发生时间置初值ve[n-1]
            vl[i]=ve[n-1];
        //按逆拓扑次序求每个事件的最迟发生时间
        for(i=n-1; i>=0; i--) {
            k=topo[i];                    //取得逆拓扑序列中的顶点序号k
            AdjNode *p=G.Vex[k].first;    //p指向k的第一个邻接顶点
            while(p!=NULL) {
                //依次更新k的所有邻接顶点的最早发生时间
                j=p->v;                   //j为邻接顶点的序号
                if(ve[j]<ve[k]+p->weight)   //更新顶点j的最早发生时间ve[j]
                    ve[j]=ve[k]+p->weight;
                p=p->next;                //p指向k的下一个邻接顶点
            }
        }
        for(i=0; i<n; i++)        //给每个事件的最迟发生时间置初值ve[n-1]
            vl[i]=ve[n-1];
        //按逆拓扑次序求每个事件的最迟发生时间

        for(i=n-1; i>=0; i--) {
            k=topo[i];                    //取得逆拓扑序列中的顶点序号k
            AdjNode *p=G.Vex[k].first;    //p指向k的第一个邻接顶点
            while(p!=NULL) {
                //根据k的邻接点,更新k的最迟发生时间
                j=p->v;                    //j为邻接顶点的序号
                if(vl[k]>vl[j]-p->weight)   //更新顶点k的最迟发生时间vl[k]
                    vl[k]=vl[j]-p->weight;
                p=p->next;                //p指向k的下一个邻接顶点
            }
        }
        cout<<"事件的最早发生时间和最迟发生时间:"<<endl;
        for(i=0; i<n; i++)
            cout<<ve[i]<<"\t"<<vl[i]<<endl;

        //判断每一活动是否为关键活动
        cout<<"关键活动路径权值之和为:"<<vl[n-1]<<endl;
        cout<<endl;
        cout<<"关键活动路径为:";
        for(i=0; i<n; i++) {   //每次循环针对vi为活动开始点的所有活动
            AdjNode *p=G.Vex[i].first;    //p指向i的第一个邻接顶点
            while(p!=NULL) {
                j=p->v;                   //j为i的邻接顶点的序号
                e=ve[i];              //计算活动<vi, vj>的最早开始时间e
                l=vl[j]-p->weight;   //计算活动<vi, vj>的最迟开始时间l
                if(e==l)              //若为关键活动,则输出<vi, vj>
                    cout<<"<"<<G.Vex[i].data<<","<<G.Vex[j].data<<">    ";
                p=p->next;            //p指向i的下一个邻接顶点
            }
        }
        return true;
    }
}
寻找关键路径
原文地址:https://www.cnblogs.com/CLGYPYJ/p/15629154.html