转自http://blog.csdn.net/jkay_wong/article/details/6696701
以边表示活动,以顶点表示事件的有向网称为AOE(activity on edge)网.AOE网是一个
有向无环图,权值表示活动持续的时间。可以用AOE网来估计工程完成的时间。由于工程
只有一个开始点和一个完成点,所以在无环路的条件下,网中只有一个入度为0的点和一
个出度为0的点.
下面是几个和AOE网有关的概念:
(1)路径长度:路径上各个活动的持续时间之和
(2)完成工程的最短时间:由于AOE网中有活动是并行进行的,所以完成工程的最短时间
就是从开始点到完成点的最长路劲长度。
(3)活动最早开始时间(earlist time)(e(i)):从开始点到顶点vi的最长路径称为事件vi的最早发生时间,
这个时间决定了以vi为尾的弧表示的活动的最早开始时间.
(4)活动最晚开始时间(latest time)(l(i)):在不推迟整个工程完成的前提下,活动最迟开始的时间
(5)完成活动的时间余量:该活动的最迟开始时间减去最早开始时间
(6)关键路径(critical path):路径长度最长的路径称为关键路径
(7)关键活动(critical activity):关键路径上的活动称为关键活动,关键活动的特点是:e(i)=l(i)
分析关键路径的目的就是辨别在整个工程中哪些是关键活动,以便争取提高关键活动的工作
效率,缩短整个工程的工期。
文件"aoe.h"
- #include<iostream>
- #include<string>
- #include<stack>
- #include<iomanip>
- using namespace std;
- const int MAX_VEX_NUM=20;
- int ve[20];//全局变量,存放各个事件的最早发生时间
- class ArcNode //表结点
- {
- public:
- int adjvex;
- int info;//权值
- ArcNode *nextarc;
- };
- class VNode //头结点
- {
- public:
- string data;
- int indegree; //顶点的入度
- ArcNode *firstarc;
- };
- class ALGraph
- {
- private:
- VNode vertices[MAX_VEX_NUM];
- int arcnum;
- int vexnum;
- public:
- void Create_ALG()
- {
- //构造有向网
- string v1,v2;
- int i,j,w;
- ArcNode *p=NULL;
- cout<<"输入顶点数和边数:";
- cin>>vexnum>>arcnum;
- cout<<"输入顶点名称:";
- for(i=0;i<vexnum;i++)
- {
- cin>>vertices[i].data;
- vertices[i].firstarc=NULL;
- vertices[i].indegree=0;
- }
- for(int k=0;k<arcnum;k++)
- {
- cout<<"按照尾->头的顺序输入每条边对应的两个顶点和该边的权值:";
- cin>>v1>>v2>>w;
- i=Locate_Vex(v1);
- j=Locate_Vex(v2);
- while(i==-1 || j==-1)
- {
- cout<<"输入的顶点错误,重新输入:";
- cin>>v1>>v2;
- i=Locate_Vex(v1);
- j=Locate_Vex(v2);
- }
- p=new ArcNode;
- p->adjvex=j;
- p->info=w;
- p->nextarc=vertices[i].firstarc;
- vertices[i].firstarc=p;
- vertices[j].indegree+=1; //作为有向弧的头的顶点入度加1
- }
- cout<<"有向网构造完成"<<endl;
- }
- int Locate_Vex(string v) //求顶点在顶点数组中的位置
- {
- for(int k=0;k<vexnum && vertices[k].data!=v;k++);
- if(k<vexnum)
- return k;
- else
- return -1;
- }
- /*关键路径求解思想*/
- /*---------------------------------------------------------------
- / 辨别关键路径就是要找出l(i)=e(i)的活动,为了求AOE网中的e(i)和l(i)
- / 首先应该求出每个事件的最早发生时间ve(j)和最晚发生时间vl(j),如果
- / 活动ai用弧<j,k>表示,那么持续时间记为dut(<j,k>).则有如下关系:
- / e(i)=ve(j), l(i)=vl(k)-dut(<j,k>);
- / 求事件的vj的最早发生时间ve(j)和最迟发生时间vl(j)要分两步进行:
- / (1):从ve(0)=0(假设顶点0是开始点)开始,根据下面公式计算其它事件
- / 的最早开始时间: ve(j)=Max{ve(i)+dut(<i,j>)} 其中i是j的所有直接前驱的集合
- / (2)从vl(n-1)=ve(n-1)开始,根据下面公式计算其他事件的最晚开始时间:
- / vl(i)=Min{vl(j)-dut(<i,j>)} 其中j是i的直接后继的集合
- / 上述两个公式必须分别在拓扑有序和逆拓扑有序的前提下进行,也就是说ve(j-1)
- / 必须在vj全部直接前驱的最早发生时间都求得以后才能确定。而vl(j-1)则必须在
- / vj的所有直接后继的最晚发生时间求得之后才能确定,因此可以在拓扑排序的基础
- / 上计算所有事件的ve(j-1)和vl(j-1).
- / 为了能按逆拓扑有序序列的顺序计算各个顶点的vl值,需记下在拓扑排序的过程中
- / 求得的拓扑有序序列,这只需要增加多一个栈,用来存储拓扑有序序列即可.
- / 由于栈的结构特点,拓扑有序序列出栈就变成逆拓扑有序序列了.
- /----------------------------------------------------------------*/
- //求所有事件的最早发生时间
- bool Topo_Order(stack<int> &T)
- {
- stack<int> s;
- ArcNode *p=NULL;
- for(int i=0;i<vexnum;i++)
- if(!vertices[i].indegree)
- s.push(i);
- int count=0;
- for(i=0;i<vexnum;i++)
- ve[i]=0; //设各顶点最早发生为0
- while(!s.empty())
- {
- int k=s.top();
- s.pop();
- T.push(k);
- count++;
- for(p=vertices[k].firstarc;p;p=p->nextarc)
- {
- int w=p->adjvex;
- if(vertices[w].indegree)
- vertices[w].indegree--;
- if(!vertices[w].indegree)
- s.push(w);
- if(ve[k]+p->info>ve[w]) //求ve[w]的最早发生时间
- ve[w]=ve[k]+p->info;
- }
- }
- if(count<vexnum)
- return 0;
- else
- return 1;
- }
- //求所有事件的最晚发生时间并求出关键活动和关键路径
- void Critical_Path()
- {
- stack<int> T;
- string cp[10];
- int c=0;
- if(!Topo_Order(T))
- {
- cout<<"该有向网有环!"<<endl;
- return;
- }
- int vl[20];
- for(int i=0;i<vexnum;i++)
- vl[i]=ve[vexnum-1]; //初始化顶点事件最迟发生时间
- while(!T.empty())
- {
- ArcNode *p=NULL;
- int j=T.top();
- T.pop();
- for(p=vertices[j].firstarc;p;p=p->nextarc)
- {
- int k=p->adjvex;
- int dut=p->info;
- if(vl[k]-dut<vl[j])
- vl[j]=vl[k]-dut;
- }
- }
- //下面是根据关键活动的特点(最早开始时间和最晚开始时间相等)求关键活动,做上标记
- cout<<"tail head weight earliest time latest time tag "<<endl;
- for(int j=0;j<vexnum;j++)
- for(ArcNode *p=vertices[j].firstarc;p;p=p->nextarc)
- {
- int k=p->adjvex;
- int dut=p->info;
- int ee=ve[j];
- int el=vl[k]-dut;
- char tag;
- tag=(ee==el)?'*':' ';
- cout<<setw(3)<<vertices[j].data<<setw(6)<<vertices[k].data<<setw(7)<<dut<<setw(9)<<ee<<setw(16)<<el<<setw(9)<<tag<<endl;
- if(tag=='*' && j==0)
- {
- cp[c]=vertices[j].data;
- c++;
- cp[c]=vertices[k].data;
- c++;
- }
- else if(tag=='*' && j!=0)
- {
- cp[c]=vertices[k].data;
- c++;
- }
- }
- cout<<"The critical activities are ended with * "<<endl;
- cout<<"So the critical path is :";
- for(i=0;i<c-1;i++)
- cout<<cp[i]<<"->";
- cout<<cp[c-1]<<endl;
- }
- };
主函数"main.cpp"
- #include"aoe.h"
- int main()
- {
- ALGraph G;
- G.Create_ALG();
- G.Critical_Path();
- cout<<endl;
- return 0;
- }
输入和输出结果:
- 输入顶点数和边数:6 8
- 输入顶点名称:v1 v2 v3 v4 v5 v6
- 按照尾->头的顺序输入每条边对应的两个顶点和该边的权值:v1 v2 3
- 按照尾->头的顺序输入每条边对应的两个顶点和该边的权值:v1 v3 2
- 按照尾->头的顺序输入每条边对应的两个顶点和该边的权值:v2 v5 3
- 按照尾->头的顺序输入每条边对应的两个顶点和该边的权值:v2 v4 2
- 按照尾->头的顺序输入每条边对应的两个顶点和该边的权值:v3 v4 4
- 按照尾->头的顺序输入每条边对应的两个顶点和该边的权值:v3 v6 3
- 按照尾->头的顺序输入每条边对应的两个顶点和该边的权值:v4 v6 2
- 按照尾->头的顺序输入每条边对应的两个顶点和该边的权值:v5 v6 1
- 有向网构造完成
- tail head weight earliest time latest time tag
- v1 v3 2 0 0 *
- v1 v2 3 0 1
- v2 v4 2 3 4
- v2 v5 3 3 4
- v3 v6 3 2 5
- v3 v4 4 2 2 *
- v4 v6 2 6 6 *
- v5 v6 1 6 7
- The critical activities are ended with *
- So the critical path is :v1->v3->v4->v6
- Press any key to continue
根据输入所生成的有向网如下所示:
当关键路径只有一条时,输出关键路径是对的,当关键路径不知一条时就是错的,但是依然是可以找出所有的关键活动,路径输出的算法,以后会完善到可以输出所以关键路径
求逆拓扑有序序列 除了利用求拓扑有序时进栈外,还可以直接用DFS遍历该有向图,直到该结点的所有邻接结点都输出之后,才将其输出,这个序列就是逆拓扑有序序列