预备知识:
AOV(Activity On Vertex)网,也叫做顶点活动网
。指的是用顶点表示的是时间,用边集表示的是时间发生的先后顺序。
AOE(Activity On Edge)网,也叫做边活动网
。指的是用带权的边集表示活动,用顶点表示事件的有向图,也就是经过该活动之后就可以发生这个事件了。
注意这两种是不同的模型,但是AOV网也可以在一定程度上转化为AOE网,就是把一个结点拆成两个点,然后从两个点之间添加一条边表示权,两个网都是有向无环图。
最长路径
:我们之前学过最短路径的求法,而最长路径顾名思义就是把图上的每个点都经过一遍后经过的最长的路径大小。最长路径的求法我们一般是先将
每个边的边权乘以-1
,然后利用SPFA算法或者是BF算法来求最短路径的长度,求出来的就是我们要求的最长路径的长度了。这个很好理解,如果我们求出的最短路径是负数的话,那么将其取相反数之后所得到的就是最大的,如果是正数的话同理可得。
关键路径
:在AOE网上的最长的路径就是我们要求的关键路径,关键路径直接影响整个工程的完成时间的长短。
我们先来说说一个活动i,我们做这个活动的时间是20分钟的话,那么我们假如有40分钟的时间间隔内都可以做这个事情,但我们只需20分钟去做,那么我们这个时间就是一个弹性的时间,对于活动i,我们把活动i最早发生的时间称为最早发生时间,我们用数组e[i]表示,活动i最晚发生时间称为最晚发生时间,我们用数组l[i]表示
。然后对于关键路径来说,它的最早和最晚的发生时间是相同的,也就是这个活动是不能够早一分钟或者晚一分钟的,我们将这个事件称为是关键活动(注意AOE中边表示的是活动),有所有的关键活动组成的路径称为关键路径,关键路径就是我们求得最长路径。
下面,我们看看这个:
我们既然要求出关键路径,那肯定要先确定一下关键活动的时间怎么求?既然活动有最早和最晚的发生时间,那么事件肯定也有最早和最晚的发生时间,我们分别用ve[i],vl[i]表示出来,假如我们知道了u时间的最早发生时间,那么我们的活动只要在u活动最早发生时间发生就是我们活动a的最早发生时间就是e[a]=ve[u],如果我们的活动a的最晚发生时间是l[a],那么在活动a最晚发生时间后再加上发生a活动的时间就是我们的事件v发生的最晚事件,即:l[a]+length[a]=vl[v],也等于l[a]=vl[v]-length[a]。这样我们就得到了活动a的最早和最晚的发生时间。
但是,我们的事件的发生可能是要之前几个事件发生之后才会发生的,也就是下面这种情况:
假如我们要求出j事件的最早发生时间,
那么肯定是要再i的一系列事件都发生完之后才能发生j事件
,所以我们的ve[j]=max{ve[i1]+length[i1],ve[i2]+length[i2],ve[i3]+length[i3]}
看这个图:
我们把t1,t2,t3看成是上面事件发生的最早的事件,由于我们的j事件要在这三件事都发生之后才能发生,那肯定是找这些时间中最大的那个时间。
同理,我们来看下面这种情况:
如果我们已经知道了后面的j系列事件的最晚的发生事件,那么我们的i事件的最晚发生时间就是ve[i]=min{vl[j1]+length1,vl[j2]+length2,vl[j3]+length3}怎么理解呢?就是
我们的j系列事件都是在i事件发生之后进行的,我们的vl[j]表示的是我们的j事件最迟最迟要在那个时候发生,不能再往后推一秒钟了,那么我们的j系列事件的vl[j]+length最小的那个事件就是我们的i事件发生的最晚的时间,我们的i事件不能再往后推迟一秒发生,不然后面的也要进行相应的推迟。
还是这个图,我们把t1,t2,t3看成是j系列事件发生的最晚的时间,我们的i时间是在j系列事件发生之前发生的,那么我们的i事件发生的最晚的事件肯定是这些时间中最早的那个时间。
通过以上的两个例子,我们可以发现我们如果想求一个事件的最早发生时间的话,是从前面往后面求得,如果想求事件的发生最晚时间,是从后面往前面开始求得。这个是不一样的,所以,从前往后求,我们可以用队列来完成,从后面往前面求,我们可以用栈来完成。
stack<int> topOrder;
//利用队列更新ve数组,同时将结点放入stack里面
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;
}
fill(vl,vl+n,ve[n-1]); //vl数组的初始化,初始为终点的ve的值,注意下标
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;
}
}
}
求关键路径(最长路径)板子代码:
const int maxn=100;
stack<int> topOrder;
struct node{
int v,w;
};
vector<node> G[maxn];
//利用队列更新ve数组,同时将结点放入stack里面
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;
}
//接下来是找到vl数组
fill(vl,vl+n,ve[n-1]); //vl数组的初始化,初始为重点的ve的值
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;
}
}
}
for(int i=0;i<n;i++) {
for(int j=0;j<G[u].size();j++){
int v=G[u][j].v,w=G[u][j].w;
int e=ve[i],l=vl[v]-w;
if(e==l){
printf("%d->%d\n",u,v);
}
}
}
return ve[n-1]; //返回关键路径的长度
}