文章目录
1 概念
1.1 AOV网、AOE网(图中都不应存有环)
1.1.1 概念
- 顶点活动(Activity On Vertex,AOV)网:用顶点表示活动,用边集表示活动间优先关系的有向图。
图1 - 边活动(Activity On Edge, AOE)网:用点权边集表示活动,而用顶点表示事件的有向图,其中边权表示完成活动所用的事件。
- AOE一般用于表示一个工程的进行过程,而工程常常可以分为若干自工程(即活动)
- 对于工程来说,总会一个起始时刻和结束时刻,因此AOV网一般只有一个源点S(入度为0的点)和一个汇点T(出度为0的点)
- 如下图所示,新添了四条边a9,a10,a11,a12,边权都为0.
1.1.2 AOV转为AOE
如果给定AOV网中各顶点活动所需的时间,那么就可以把AOV网转为AOE网。
方法为:
- 1 将AOV网中每个顶点都拆成两个顶点,分别表示活动的起点和终点,而两个顶点之间用有向边连接,该有向边表示原顶点的活动,边权给定;
- 2 原AOV网中的边全部视为空活动,边权为0;
图1转为AOE网,得:
1.2 关键路径、关键活动
关键路径:AOE网中的最长路径。(工程起始到终止的时间需要多少)
关键活动:关键路径上的活动。(那些路径上的活动是影响整个工程进度的关键)
1.3 最长路径
最长路径(Long Path Problem)问题:寻找图中的最长简单路径。
- 对于没有正环的图(从源点可达的正环),求最短路径长度:
- 把所有边的边权乘以-1,令其变为相反数,然后用Bellman-Ford算法或SPFA算法,求最短路径长度,将所得结果取反即可。
- 如果图中有正环,那么最长路径是不存在的。但是,如果需要求最长简单路径(每个顶点最多只经过一次的路径)是存在的,但却没有办法通过Bellman-Ford等求解,原因是最长路径问题是NP—Hard问题。
2 求解关键路径
由于AOE是有向无环图,而关键路径是图中的最长路径,因此下面给出的是求解有向无环图(DAG)中最长路径的方法。
2.1 准备:
- e[r]:活动a~r~开始的最早开始时间;
- l[r]:活动a~r~开始的最迟开始时间;
+ 判断 e[r]是否等与 l[r]来判断是否为关键活动
- ve[i]:事件i的最早发生事件;
- vl[i]: 事件i的最迟发生事件;
以下图为例:
- 1 对于活动ar来说,只要在事件Vj最早发生时马上开始,就可以使得活动ar的开始事件最早,因此e[r] = ve[i];
- 2 如果l[r]是活动的ar的最迟发生时间,那么l[r] + lengh[r](lengh[r]表示活动ar的边权)就是事件Vj的最迟发生事件,因此l[r] = vl[j] - lenght[r]
因此只需求出数组ve和数组vl,就可以根据上面的公示求出数组e和数组l。
2.2 求数组ve
2.2.1 思路
如上图所示,事件Vj的最早发生时间为ve[i1] + length[r1] ~ ve[ik]+ lengh[rk]中的最大值(即所有能到达Vj的活动都完成之后,Vj才能被“”激活);
因此如果想获得ve[j]的正确值,ve[i1]~ve[ik]必须已经得到,有什么方法能够在访问某个结点时候,保证它的前驱结点都已经访问完毕?用拓扑排序就可以。
在拓扑排序访问某个结点时,不是让它去找前驱结点来更新ve[i],而是使用ve[i]去更新所有后继结点的ve值。
2.2.2 实现代码
stack<int> topOrder;//拓扑序列
bool topologicalSort(){
queue<int> q;
for (int i = 0; i < n; ++i)
{
if(inDegree[i] == 0){
q.push(i);
}
}
while(!q.empty()){
int u = q.front();
q.pop();
topOrder.push(u);//将u加入拓扑序列
for (int i = 0; i < G[u].size(); ++i)
{
int v = G[u][i].v;//u的i号后继结点编号为v
inDegree[v]--;
if(inDegree[v] == 0){
q.push(v);
}
//用ve[u]更新u的所有后继结点v
if(ve[u] + G[u][i].w > ve[v]){
ve[v] = ve[u] + G[u][i].w;
}
}
}
if(topOrder.size() == n) return true;
else return false;
}
2.3 求数组vl
2.3.1 思路
由上图可知,如果要求出vl[i]的正确值,vl[j1]~vl[jk]都必须已经得。这与求ve数组正好相反,也就是在访问某个结点时保证它的后继结点都已经访问完毕。
颠倒拓扑排序,来得到一组合法的逆拓扑序列。
上面是实现栈来存储拓扑序列,只要要顺序出栈就是逆拓扑序列。
fill(vl, vl + n, ve[n - 1]);//vl初始化,初值值为汇点ve的值
//直接用topOrder出栈即逆序拓扑序列,求解vl数组
while(!topOrder.empty()){
int u = topOrder.top();
topOrder.pop();
for (int i = 0; i < G[u].size(); ++i)
{
int v = G[u][i].v;
if(vl[v] - G[u][i].w < vl[u]){
vl[u] = vl[v] - G[u][i].w;
}
}
}
2.4 求关键路径
2.4.1 思路( 先求点,再夹边)
- 1 拓扑序列和逆拓扑序列分别计算各事件的最早、最迟发生时间:
- 最早(拓扑序列) :
- 最迟(逆拓扑序列:
- 最早(拓扑序列) :
- 2 用上面的结果各边(活动)的最早开始时间和最迟开始时间
- 最早:
- 最迟:
- 最早:
- 3 的活动即为关键活动。
2.4.2 实现代码
//关键路径,不是有向无环图返回-1,否则返回关键路径长度
int CriticalPath(){
memset(ve, 0, sizeof(ve));
if(topologicalSort() == false){
return -1;//不是有向无环图返回-1
}
fill(vl, vl + n, ve[n - 1]);//vl初始化,初值值为汇点ve的值
//直接用topOrder出栈即逆序拓扑序列,求解vl数组
while(!topOrder.empty()){
int u = topOrder.top();
topOrder.pop();
for (int i = 0; i < G[u].size(); ++i)
{
int v = G[u][i].v;
if(vl[v] - G[u][i].w < vl[u]){
vl[u] = vl[v] - G[u][i].w;
}
}
}
//遍历邻接表所有边,计算活动最早开始时间e和最迟开始时间l
for (int u = 0; u < n; ++u)
{
for (int i = 0; i < G[u].size(); ++i)
{
int v = G[u][i].v;
int w = G[u][i].w;
int e = ve[u];//活动最早开始时间
int l = vl[u] - w;//或最迟开始时间
if(e == l){//关键活动
printf("%d->%d\n", u, v);
}
}
}
return ve[n -1];//返回关键路径长度
}
2.5 注意
- 如果实现不知道汇点编号,可以通过取ve数组的最大值。
- 因为ve数组为事件的最早开始事件,因此所有事件中ve最大的一定是最后一个(或多个)事件,也就是汇点
实现代码:
int maxLength = 0;
for (int i = 0; i < n; ++i)
{
if(ve[i] > maxLength){
maxLength = ve[i];
}
}
fill(vl, vl + n, ve[n - 1]);//vl初始化,初值值为汇点ve的值
问题 A: 关键路径
[命题人 : 外部导入]
时间限制 : 1.000 sec 内存限制 : 128 MB
题目描述
描述:
图的连接边上的数据表示其权值,带权值的图称作网。
上图可描述为顶点集为(a,b,c,d,e)
边集及其权值为(始点,终点 权值):
a b 3
a c 2
b d 5
c d 7
c e 4
d e 6
网的源点是入度为0的顶点,汇点是出度为0的顶点。网的关键路径是指从源点到汇点的所有路径中,具有最大路径长度的路径。上图中的关键路径为a->c->d->e,其权值之和为关键路径的长度为15。
本题的要求是根据给出的网的邻接矩阵求该网的关键路径及其长度。
输入
第一行输入一个正整数n(1<=n<=5),其代表测试数据数目,即图的数目
第二行输入x(1<=x<=15)代表顶点个数,y(1<=y<=19)代表边的条数
第三行给出图中的顶点集,共x个小写字母表示顶点
接下来每行给出一条边的始点和终点及其权值,用空格相隔,每行代表一条边。
输出
第一个输出是图的关键路径(用给出的字母表示顶点, 用括号将边括起来,顶点逗号相隔)
第二个输出是关键路径的长度
每个矩阵对应上面两个输出,两个输出在同一行用空格间隔,每个矩阵的输出占一行。
样例输入
2
5 6
abcde
a b 3
a c 2
b d 5
c d 7
c e 4
d e 6
4 5
abcd
a b 2
a c 3
a d 4
b d 1
c d 3
样例输出
(a,c) (c,d) (d,e) 15
(a,c) (c,d) 6
提示
作者:梁青青
3.2 参考代码:
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <cstring>
#include <map>
using std::queue;
using std::stack;
using std::vector;
using std::fill;
using std::map;
struct node
{
int v;
int w;
node(int _v, int _w):v(_v), w(_w){}
};
const int MAXN = 20;
vector<node> G[MAXN];//图
int ve[MAXN], vl[MAXN];
int N;
stack<int> topOrder;
int inDegree[MAXN];
map<char, int> letterToIndex;//字母映射数字
vector<int> ans[MAXN];//关键路径
char str[MAXN];//输入的字母串
//拓扑排序
bool topologicalSort(){
queue<int> q;
for (int i = 0; i < N; ++i)
{
if(inDegree[i] == 0){
q.push(i);
}
}
while(!q.empty()){
int u = q.front();
q.pop();
topOrder.push(u);
for (int i = 0; i < G[u].size(); ++i)
{
int v = G[u][i].v;
inDegree[v]--;
if(inDegree[v] == 0){
q.push(v);
}
if(ve[u] + G[u][i].w > ve[v]){
ve[v] = ve[u] + G[u][i].w;
}
}
}
if(topOrder.size() == N) return true;
else return false;
}
int CriticalPath(){
memset(ve, 0, sizeof(ve));
if(topologicalSort() == false){//求ve
return -1;
}
int maxLength = 0;
for (int i = 0; i < N; ++i)
{
if(ve[i] > maxLength){
maxLength = ve[i];
}
}
fill(vl, vl + MAXN, maxLength);
while(!topOrder.empty()){//逆拓扑排序,求vl
int u = topOrder.top();
topOrder.pop();
for (int i = 0; i < G[u].size(); ++i)
{
int v = G[u][i].v;
if(vl[v] - G[u][i].w < vl[u]){
vl[u] = vl[v] - G[u][i].w;
}
}
}
for (int i = 0; i < N; ++i)//清空上一次关键路径
{
ans[i].clear();
}
for (int u = 0; u < N; ++u)
{
for (int i = 0; i < G[u].size(); ++i)
{
int v = G[u][i].v, w = G[u][i].w;
int e = ve[u], l = vl[v] - w;
if(e == l){//是关键活动,则存入关键路径
ans[u].push_back(v);
}
}
}
int s;
for (int i = 0; i < N; ++i)//寻找事件起始点
{
if(ve[i] == 0){
s = i;
break;
}
}
while(ans[s].size()){//没有后继结点时,退出
printf("(%c,%c) ", str[s], str[ans[s][0]]);
s = ans[s][0];
}
return ve[N - 1];//return maxLength;
}
int main(int argc, char const *argv[])
{
int n, m, w;
char a, b;
scanf("%d", &n);
for (int i = 0; i < n; ++i)
{
memset(inDegree, 0, sizeof(inDegree));
scanf("%d%d", &N, &m);
for (int j = 0; j < N; ++j)
{
G[j].clear();
}
scanf("%s", str);
for (int j = 0; j < N; ++j)
{
letterToIndex[str[j]] = j;
}
for (int j = 0; j < m; ++j)
{
//getchar();
scanf("%*c%c %c %d", &a, &b, &w);
G[letterToIndex[a]].push_back(node(letterToIndex[b], w));
inDegree[letterToIndex[b]]++;
}
int length = CriticalPath();
printf("%d\n", length);
}
return 0;
}