关键路径 图论 ——数据结构课程
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; } }