AOE-网就是用弧表示活动,是一种带权重的有向无环图,其中,顶点表示事件,弧表示活动,权表示活动持续的时间。AOE网在工程时间估算上有较广泛应用。如右图
由于AOE网中有些活动可以并行进行,故完成工程的最短时间是从开始点到完成点的最长路径的长度。
这里我们要特别注意节点与弧的区别和联系。节点是瞬时事件,而弧是要持续一段时间的。节点的最早开始时间就是相应弧的最早开始时间。为了求得活动的最早与最迟开始时间,用ee,el表示,我们需要先求出其开始节点的最早,最迟开始时间,用数组ve[ ] ,vl[ ]表示。而ee==el的活动即为关键活动,关键路径就是有关键活动组成,他们从开始点到完成点,长度最长的路径。只有提前完成关键活动,才能缩短整个工期。
求ve[ ],vl[ ]:
第一步是从前往后推:
第二步是从后往前推,完成点是转折点,也就是说vl[完成点]==ve[完成点]。所谓的前后不仅以开始点、完成点为标识,而且指拓扑有序和拓扑逆序。为了得到拓扑逆序我们用一个栈来记录求拓扑有序,这样从栈顶到栈底就是逆序了。
求得ve[ ],vl[ ]后,就可以求各个活动的最早、最迟开始时间。
若ee(i)==el(i),我们把他记录下来,一遍打印全部的关键活动,和后面求关键路径。
下面是源码。首先是Graph.h文件,记录了构造有向图的数据结构,采用邻接表来存储,求得是首图的关键路径。
#ifndef _Graph_H
#define _Graph_H
#define MAX_VERTEX_NUM 20
#define InfoType double
#define VertexType int
typedef struct ArcNode {
int adjvex;
struct ArcNode *nextarc;
InfoType info;
}ArcNode;
typedef struct VNode {
VertexType data;
ArcNode *firstarc;
}VNode,AdjList[MAX_VERTEX_NUM];
typedef struct {
AdjList vertices;
int vexnum, arcnum;
int kind;
}ALGraph;
//全局变量
ALGraph *graph;
#endif
下面是CriticalPath.cpp文件。因为可能存在多条关键路径,用到了回溯法。
#pragma warning(disable:4996)
#include"Graph.h"
#include<iostream>
#include<stdlib.h>
#include<stack>
#include<vector>
#include<map>
#include<set>
#include<string>
#include<utility>
#include<stdio.h>
using namespace std;
vector<double>ve;//存储各顶点事件的最早开始时间
vector<double>vl;//存储最迟开始时间
map<string,pair<double,double>>Act;//存储各活动的最早和最迟开始时间
stack<int>T;//存储逆序
char buf[3 * MAX_VERTEX_NUM];//存储最长关键路径
vector<int>TopOrder;//存储拓扑序列
map<int, vector<int>>mp;//存储关键活动
int flag = 0;//节点是否存储信息的开关
//图中各节点从0编号
ALGraph * Establish_Graph() {
int k;
int arcnum;
char c = 'y';
ALGraph * graph = (ALGraph *)malloc(sizeof(ALGraph));
graph->kind = 1;
cout << "输入图的顶点个数与弧的个数" << endl;
cin >> graph->vexnum;
cin >> graph->arcnum;
for (int i = 0;i < graph->vexnum;i++) {
if (flag == 0)
graph->vertices[i].data = i;
else
cin >> graph->vertices[i].data;
graph->vertices[i].firstarc = NULL;
cout << "\t" << "输入顶点"<<i<<"的出弧de头部顶点、弧信息(权重)" << endl;
//构建弧顶点
cout << "该顶点出弧的个数" << endl;
cin >> arcnum;
k = 0;
while (k<arcnum) {
cout << "第" << k << "条弧" << endl;
ArcNode *arc = (ArcNode*)malloc(sizeof(ArcNode));
cin >> arc->adjvex;
cin >> arc->info;
arc->nextarc = graph->vertices[i].firstarc;//把弧插到头部
graph->vertices[i].firstarc = arc;
k++;
}
}
return graph;
}
void free(ALGraph *G) {
ArcNode *ptr,*temp;
for (int i = 0;i < G->vexnum;i++) {
ptr = G->vertices[i].firstarc;
while (ptr) {
temp = ptr->nextarc;
delete ptr;
ptr = temp;
}
}
delete[] &G->vertices;
}
//求图中各顶点的入度函数
int * Indegree(ALGraph *graph) {
ArcNode *arcptr;
int *indegree = (int *)malloc(sizeof(int)*graph->vexnum);
for (int i = 0;i < graph->vexnum;i++)
indegree[i] = 0;
for (int i = 0;i < graph->vexnum;i++) {
for (arcptr = graph->vertices[i].firstarc;arcptr != NULL;arcptr=arcptr->nextarc) {
indegree[arcptr->adjvex]++;
}
}
return indegree;
}
//T记录拓扑逆序
int TopologicalOrder(ALGraph *G, stack<int> &T) {
stack<int>S;
ArcNode *ptr;
int *degree;
int count = 0;
int v,k;
for (int i = 0;i < G->vexnum;i++)
ve.push_back(0);
degree = Indegree(G);
for (int i = 0;i < G->vexnum;i++)
if (degree[i] == 0)
S.push(i);
while (!S.empty()) {
v = S.top();
T.push(v);
TopOrder.push_back(v);
S.pop();
++count;
for (ptr = G->vertices[v].firstarc;ptr;ptr = ptr->nextarc) {
k = ptr->adjvex;
if (--degree[k] == 0)
S.push(k);
if (ve[v] + ptr->info > ve[k])
ve[k] = ve[v] + ptr->info;
}
}
delete[] degree;
if (count < G->vexnum)
return -1;
else
return 0;
}
void show(vector<int> &arr) {
char buf[3 * MAX_VERTEX_NUM];
char* ptr=buf;
int len;
vector<int>::iterator pr= arr.begin();
while (pr !=arr.end()) {
if ((len = snprintf(ptr, 5, "%d->", *pr)) < 0)
cout << "snprintf error:" << strerror(errno) << endl;
ptr =ptr+len;
pr++;
}
snprintf(ptr, 5, "END\n\0");
cout << buf << endl;
}
//回溯算法打印所有可能的关键路径
void show_critical(int vexnum) {
int i = 1;//第0层为原点
vector<int> k{0,0};//头两个元素初始化为0
vector<int> x{ 0 };//记录关键路径上各个节点
int it, flag = 0;
//k[i]记录第i层元素在set中的index
while (i >= 1) {
while (k[i] < mp.find(x[i - 1])->second.size()) {
it=*((mp.find(x[i-1])->second.begin())+k[i]);
x.push_back(it);//x[i]
if (x[i] == vexnum - 1){
flag = 1;
show(x);
break;
}
else if (mp.find(x[i]) != mp.end()) {
i = i + 1;
k.push_back(0);//k[i] = 0;
}
else {
x.pop_back();
k[i] = k[i] + 1;
}
}
if (flag == 1) {
x.pop_back();
flag = 0;
}
i--;
x.pop_back();
k[i] += 1;
}
}
int CriticalPath(ALGraph *G) {
double ee;//记录各弧上活动的最早开始时间
double el;//活动最迟开始时间
ArcNode *ptr;
int j, k;
int len;
vector<int>st;
map<int, vector<int>>::iterator mptr;
char str[10];
if (TopologicalOrder(G, T)<0)
return -1;
for (int i = 0;i < G->vexnum;i++)
vl.push_back(ve[G->vexnum-1]);
while (!T.empty()) {
for (j = T.top(),ptr = G->vertices[j].firstarc;ptr;ptr = ptr->nextarc) {
k = ptr->adjvex;
if (vl[k] - ptr->info < vl[j])
vl[j] = vl[k] - ptr->info;
}
T.pop();
}
for (j = 0;j < G->vexnum;j++) {
for (ptr = G->vertices[j].firstarc;ptr;ptr = ptr->nextarc) {
k = ptr->adjvex;
ee = ve[j];
el = vl[k] - ptr->info;
if ((len = snprintf(str,5, "%d->%d :\0", j, k)) < 0)
cout << "sprintf error" << endl;
Act.insert(make_pair(str, make_pair(ee, el)));
if (ee == el){
if ((mptr=mp.find(j)) == mp.end()) {//mp中尚没有指定关键字
st.push_back(k);
mp.insert(make_pair(j, st));
}
else {
mptr->second.push_back(k);
}
}
}
st.clear();
}
cout << "关键活动:" << endl;
for (auto c : mp){
if(c.second.size()==1)
cout << c.first << "->"<< *c.second.begin() << endl;
else {
for (auto s : c.second)
cout << c.first << "->" << s << endl;
}
}
//从开始节点(编号0)搜索最长关键路径,最后一个为完成节点(可能有多条关键路径)
cout <<"最长关键路径:" << endl;
show_critical(G->vexnum);
return 0;
}
int main() {
int r;
ALGraph *graph = Establish_Graph();
if((r=CriticalPath(graph))<0)
cout<<"CriticalPath error"<<endl;
free(graph);
cout << "拓扑序列:" << endl;
for (auto c : TopOrder) {
cout << c << " ";
}
cout <<endl<< "各活动的最早与最迟开始时间:" << endl;
for (auto c : Act) {
cout << c.first << "( " << c.second.first << " , " << c.second.second << " )" << endl;
}
cout << "各节点的ve:" << endl;
for (auto c : ve)
cout << c << " ";
cout << endl << "各节点的vl:" << endl;
for (auto c : vl)
cout << c << " ";
cout << endl;
system("pause");
return 0;
}
运行结果:
经验证,上述方法可以推广,可以正确解决AOE问题。