图的应用——关键路径
Posted Rainbowman 0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图的应用——关键路径相关的知识,希望对你有一定的参考价值。
1. 基本概念
1.AOV网和AOE网
(1)AOV网:一种有向图,图中的顶点表示活动,边表示活动之间的关系。
如
上图中,顶点为课程(活动),边为课程之间的优先关系。
(2)AOE网:一种有向图,边表示活动,顶点表示事件,边上的权值表示活动持续的时间。
如:
上图中,边表示活动(买东西或运东西),权值代表活动持续的时间,顶点代表事件。
如何理解活动与事件?
边代表活动,是需要一段时间来执行的事情,比如买主机,这是活动,需要一段时间来执行。
顶点代表事件,是瞬时发生的,可以表示一些活动的开始,也可以表示一些活动的结束。
我们要研究的关键路径问题就是基于AOE网的。
2. 关键路径
AOE网用来估算工程的完成时间。网中只有一个开始点(入度为零,称为源点),一个结束点 (出度为零,称为汇点)。
AOE网的性质:
(1)只有在进入某顶点的活动都已经结束,该顶点所代表的事件才发生;
(2)只有在某顶点所代表的事件发生后,从该顶点出发的各活动才开始。
对于一个工程来讲,整个工程的完成时间为从源点到汇点的权值之和最长的路径,称这条路径为关键路径。
蓝色标出的路径即为关键路径。
关键路径上的边(活动)称为关键活动,如果关键活动的持续时间增加,则完成工程的总时间也要增加。所以称之为关键活动。
2. 求AOE网中关键路径的思路
我们知道,关键路径就是AOE网中从源点到汇点权值之和最大的那条路径。
有如下所示的AOE网,ai表示边的权值(即活动持续时间)。
首先,我们有以下定义:
符号 | 含义 |
---|---|
e(i) | 活动ai的最早开始时间 |
l(i) | 活动ai的最迟开始时间 |
l(i) - e(i) | 表示可以拖多久再开始做活动ai(即完成活动ai的时间余量) |
l(i) = e(i) | 表示活动ai为关键活动(因此关键活动不能拖,得立马开始) |
ve(j) | 顶点j(事件j)的最早发生时间 |
vl(j) | 顶点j(事件j)的最迟发生时间 |
dut(<j, k>) | 顶点j(事件j)和顶点k(事件k)之间的边(活动)所需执行事件 |
注意,对于活动(边),我们说的是开始时间,对于事件(顶点),我们说的是发生时间。这也可以帮助我们理解活动和事件的区别。
对于上图,有1——6六个顶点。
(1)求ve(i):
ve(i)的求法:首先令ve(1) = 0(顶点从1开始计数),按顺序向后遍历,ve(k) = max{ve(j) + dut(<j, k>)}。
即通过正向遍历,求出ve(i)
解释:为什么是取最大值?其实很好理解,因为只有在进入某顶点的活动都已经结束,该顶点所代表的事件才发生;(AOE网性质1)
看上图中的顶点4,它有两条路:1-2-4(因为ve(1)=0,所以此时顶点4的发生时间ve(4)为5),另一条路1-3-4(因为ve(1)=0,所以此时顶点4的发生时间ve(4)为6),显然,我们应该让选择更晚的时间作为事件4的最早发生时间(因为如果事件4的最早发生时间早于6,则1-3-4可能还没有全部完成,一个事件发生的最早时间是这一事件之前的所有活动都执行完毕的时间),因此应该取最大值。
(2)求vl(i):
vl(i)的求法:首先令vl(6) = ve(6)(即让最后一个顶点的ve和vl相等,这是因为最后一个事件的最早发生时间和最晚发生时间肯定相同),然后逆向(从6–>1)求vl(j),有vl(j) = min{vl(k) - dut(<j, k>)}。
即通过逆向遍历,求出vl(i)
解释:为什么是取最小值?因为只有在某顶点所代表的事件发生后,从该顶点出发的各活动才开始(AOE网性质2),所以某一事件的最迟发生时间应该尽可能的早,这样才能保证该事件后的所有活动都有充足的事件进行完。
(3)求e(i)和l(i):
设ai是弧<j,k>上的活动。(即j,k是顶点,ai是j、k之间的边),dut(<j,k>)表示活动ai的持续时间,则有:
e(i) = ve(i)
l(i) = vl(i) - dut(<j,k>)
解释:活动ai的最早开始时间和事件j的最早发生事件相同,活动ai的最迟开始事件是活动k的最晚开始时间减去活动ai的持续时间,静下心来想想,应该不难理解。
(4)找出e(i) = l(i)的活动(边)
我们在(3)求e(i)和l(i)的过程中,可以找出e(i) = l(i)的活动(边),这些活动就是关键活动,这些活动共同组成了关键路径。
有两点需要注意:
(1)在(1)中求ve(i)时,需要按照拓扑顺序来求,目的是判断该图中是是否有回路,若有回路,则不能求关键路径。(关于拓扑排序看这里)
(2)事件(顶点)的最早开始时间要尽可能的晚(ve(k) = max{ve(j) + dut(<j, k>)}),才能保证该事件前面的活动都发生完了。事件的最晚开始时间要尽可能的早(min{vl(k) - dut(<j, k>)}),才能保证给该事件后面的活动留有充足时间。
3. 代码
#include <iostream>
#include <stack>
#define MAX_VEX_NUM 20
#define OK 1
#define ERROR 0
typedef char NumType;
typedef int Weight;
typedef int Status;
using namespace std;
//以下是用邻接表构建有向图
struct ArcNode
{
int AdjVex;
ArcNode *NextArc;
Weight w;
};
struct VexNode
{
NumType Data;
int InDegree;
ArcNode *FirstArc;
};
struct ALGraph
{
VexNode Vex[MAX_VEX_NUM];
int VexNum;
int ArcNum;
};
int Locate(ALGraph G,NumType v)
{
int i;
for(i=0;i<G.VexNum;i++)
if(v==G.Vex[i].Data)return i;
return -1;
}
void CreatALGraph(ALGraph &G)
{
cout<<"请输入顶点个数:"<<endl;
cin>>G.VexNum;
cout<<"请输入弧的条数:"<<endl;
cin>>G.ArcNum;
cout<<"请输入顶点信息:"<<endl;
int i,j;
for(i=0;i<G.VexNum;i++){cin>>G.Vex[i].Data;G.Vex[i].FirstArc=0;G.Vex[i].InDegree=0;}
int k;
NumType v1,v2;
Weight wei;
cout<<"请输入弧的信息:"<<endl;
for(k=0;k<G.ArcNum;k++)
{
cin>>v1;
cin>>v2;
cin>>wei;
i=Locate(G,v1);j=Locate(G,v2);
G.Vex[j].InDegree++;
ArcNode *p=new ArcNode();
*p={j,G.Vex[i].FirstArc,wei};
G.Vex[i].FirstArc=p;
}
}
Status TopoLogicalSort(ALGraph G,stack<int> &T,int *ve)
{
int i;
stack<int> s;
for(i=0;i<G.VexNum;i++)
if(G.Vex[i].InDegree==0)s.push(i);
int j,k;
ArcNode *p=0;
while(!s.empty())
{
j=s.top();
s.pop();
//T用来存拓扑序列
T.push(j);
for(p=G.Vex[j].FirstArc;p!=0;p=p->NextArc)
{
k=p->AdjVex;
G.Vex[k].InDegree--;
if(!G.Vex[k].InDegree)s.push(k);
if(ve[j]+p->w>ve[k])ve[k]=ve[j]+p->w;
}
}
}
void CriticalPath(ALGraph G)
{
int ve[G.VexNum]={0};
stack<int> T;
TopoLogicalSort(G,T,ve);
int vl[G.VexNum];
int i,j,k;
for(i=0;i<G.VexNum;i++)vl[i]=ve[G.VexNum-1];//初始化,很重要
ArcNode *p=0;
while(!T.empty())
{
//反向拓扑序列输出
j=T.top();
T.pop();
for(p=G.Vex[j].FirstArc;p!=0;p=p->NextArc)
{
k=p->AdjVex;
if(vl[k]-p->w<vl[j])vl[j]=vl[k]-p->w;
}
}
int ee,el;
for(j=0;j<G.VexNum;j++)
{
for(p=G.Vex[j].FirstArc;p!=0;p=p->NextArc)
{
k=p->AdjVex;
ee=ve[j];
el=vl[k]-p->w;
if(ee==el)cout<<G.Vex[j].Data<<"-"<<G.Vex[k].Data<<endl;
}
}
}
int main()
{
ALGraph G;
CreatALGraph(G);
CriticalPath(G);
return 0;
}
以上是关于图的应用——关键路径的主要内容,如果未能解决你的问题,请参考以下文章
[从今天开始修炼数据结构]无环图的应用 —— 拓扑排序和关键路径算法