AOE网:在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,边上的权值表示活动的持续时间,称这样的有向图叫做边表示活动的网,简称AOE网。AOE网中没有入边的顶点称为始点(或源点),没有出边的顶点称为终点(或汇点)。
AOE网的性质:
⑴ 只有在某顶点所代表的事件发生后,从该顶点出发的各活动才能开始;
⑵ 只有在进入某顶点的各活动都结束,该顶点所代表的事件才能发生。
关键路径:在AOE网中,从始点到终点具有最大路径长度(该路径上的各个活动所持续的时间之和)的路径称为关键路径。
关键活动:关键路径上的活动称为关键活动。关键活动:e[i]=l[i]的活动
由于AOE网中的某些活动能够同时进行,故完成整个工程所必须花费的时间应该为始点到终点的最大路径长度。关键路径长度是整个工程所需的最短工期。
与关键活动有关的量:
⑴ 事件的最早发生时间ve[k]
ve[k]是指从始点开始到顶点vk的最大路径长度。这个长度决定了所有从顶点vk发出的活动能够开工的最早时间。
⑵ 事件的最迟发生时间vl[k]
vl[k]是指在不推迟整个工期的前提下,事件vk允许的最晚发生时间。
⑶ 活动的最早开始时间e[i]
若活动ai是由弧<vk , vj>表示,则活动ai的最早开始时间应等于事件vk的最早发生时间。因此,有:e[i]=ve[k]
⑷ 活动的最晚开始时间l[i]
活动ai的最晚开始时间是指,在不推迟整个工期的前提下, ai必须开始的最晚时间。若ai由弧<vk,vj>表示,则ai的最晚开始时间要保证事件vj的最迟发生时间不拖后。因此,有:l[i]=vl[j]-len<vk, vj>
示例:
所以:
代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
Status TopologicalOrder(ALGraph G, Stack &T)
{
// 有向网G采用邻接表存储结构,求各顶点事件的最早发生时间ve(全局变量)。
// T为拓扑序列定点栈,S为零入度顶点栈。
// 若G无回路,则用栈T返回G的一个拓扑序列,且函数值为OK,否则为ERROR。
Stack S;
int
count=0,k;
char
indegree[40];
ArcNode *p;
InitStack(S);
FindInDegree(G, indegree);
// 对各顶点求入度indegree[0..vernum-1]
for
(
int
j=0; j<G.vexnum; ++j)
// 建零入度顶点栈S
if
(indegree[j]==0)
Push(S, j);
// 入度为0者进栈
InitStack(T);
//建拓扑序列顶点栈T
count = 0;
for
(
int
i=0; i<G.vexnum; i++)
ve[i] = 0;
// 初始化
while
(!StackEmpty(S))
{
Pop(S, j); Push(T, j); ++count;
// j号顶点入T栈并计数
for
(p=G.vertices[j].firstarc; p; p=p->nextarc)
{
k = p->adjvex;
// 对j号顶点的每个邻接点的入度减1
if
(--indegree[k] == 0) Push(S, k);
// 若入度减为0,则入栈
if
(ve[j]+p->info > ve[k]) ve[k] = ve[j]+p->info;
}
//for *(p->info)=dut(<j,k>)
}
if
(count<G.vexnum)
return
ERROR;
// 该有向网有回路
else
return
OK;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
Status CriticalPath(ALGraph G)
{
// G为有向网,输出G的各项关键活动。
Stack T;
int
a,j,k,el,ee,dut;
char
tag;
ArcNode *p;
if
(!TopologicalOrder(G, T))
return
ERROR;
for
(a=0; a<G.vexnum; a++)
vl[a] = ve[G.vexnum-1];
// 初始化顶点事件的最迟发生时间
while
(!StackEmpty(T))
// 按拓扑逆序求各顶点的vl值
for
(Pop(T, j), p=G.vertices[j].firstarc; p; p=p->nextarc)
{
k=p->adjvex; dut=p->info;
//dut<j,k>
if
(vl[k]-dut < vl[j])
vl[j] = vl[k]-dut;
}
for
(j=0; j<G.vexnum; ++j)
// 求ee,el和关键活动
for
(p=G.vertices[j].firstarc; p; p=p->nextarc)
{
k=p->adjvex;dut=p->info;
ee = ve[j]; el = vl[k]-dut;
tag = (ee==el) ?
'*'
:
' '
;
printf
(j, k, dut, ee, el, tag);
// 输出关键活动
}
return
OK;
}
|
......------------------------------------------------------------------------------------------------------------------------
一、概述
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若<u,v> ∈E(G),则u在线性序列中出现在v之前。
通常,这样的线性序列称为满足拓扑次序(TopoiSicai Order)的序列,简称拓扑序列。
注意:
①若将图中顶点按拓扑次序排成一行,则图中所有的有向边均是从左指向右的。
②若图中存在有向环,则不可能使顶点满足拓扑次序。
③一个DAG的拓扑序列通常表示某种方案切实可行。
【例】一本书的作者将书本中的各章节学习作为顶点,各章节的先学后修关系作为边,构成一个有向图。按有向图的拓扑次序安排章节,才能保证读者在学习某章节时,其预备知识已在前面的章节里介绍过。
④一个DAG可能有多个拓扑序列。
【例】对图G9进行拓扑排序,至少可得到如下的两个(实际远不止两个)拓扑序列:C0,C1,C2,C4,C3,C5,C7,C8,C6和C0,C7,C9,C1,C4,C2,C3,C6,C5。
⑤当有向图中存在有向环时,拓扑序列不存在
【例】下面(a)图中的有向环重排后如(b)所示,有向边<v3,vl>和其它边反向。若有向图被用来表示某项工程实施方案或某项工作计划,则找不到该图的拓扑序列(即含有向环),就意味着该方案或计划是不可行的。
二、无前趋的顶点优先的拓扑排序方法
该方法的每一步总是输出当前无前趋(即人度为零)的顶点,其抽象算法可描述为:
NonPreFirstTopSort(G){//优先输出无前趋的顶点
while(G中有人度为0的顶点)do{
从G中选择一个人度为0的顶点v且输出之;
从G中删去v及其所有出边;
}
if(输出的顶点数目<|V(G)|)
//若此条件不成立,则表示所有顶点均已输出,排序成功。
Error("G中存在有向环,排序失败!");
}
注意:
无前趋的顶点优先的拓扑排序算法在具体存储结构下,为便于考察每个顶点的人度,可保存各顶点当前的人度。为避免每次选入度为0的顶点时扫描整个存储空间,可设一个栈或队列暂存所有入度为零的顶点:
在开始排序前,扫描对应的存储空间,将人度为零的顶点均入栈(队)。以后每次选人度为零的顶点时,只需做出栈(队)操作即可。
三、无后继的顶点优先拓扑排序方法
1、思想方法
该方法的每一步均是输出当前无后继(即出度为0)的顶点。对于一个DAG,按此方法输出的序列是逆拓扑次序。因此设置一个栈(或向量)T来保存输出的顶点序列,即可得到拓扑序列。若T是栈,则每当输出顶点时,只需做人栈操作,排序完成时将栈中顶点依次出栈即可得拓扑序列。若T是向量,则将输出的顶点从T[n-1]开始依次从后往前存放,即可保证T中存储的顶点是拓扑序列。
2、抽象算法描述
算法的抽象描述为:
NonSuccFirstTopSort(G){//优先输出无后继的顶点
while(G中有出度为0的顶点)do {
从G中选一出度为0的顶点v且输出v;
从G中删去v及v的所有人边
}
if(输出的顶点数目<|V(G)|)
Error("G中存在有向环,排序失败!");
}
3、算法求精
在对该算法求精时,可用逆邻接表作为G的存储结构。设置一个向量outdegree[0..n-1]或在逆邻接表的顶点表结点中增加1个出度域来保存各顶点当前的出度;设置一个栈或队列来暂存所有出度为零的顶点。除了增加一个栈或向量T来保存输出的顶点序列外,该算法完全类似于NonPreFirstTopSort。
四、利用深度优先遍历对DAG拓扑排序
当从某顶点v出发的DFS搜索完成时,v的所有后继必定均已被访问过(想像它们均已被删除),此时的v相当于是无后继的顶点,因此在DFS算法返回之前输出顶点v即可得到 DAG的逆拓扑序列。
其中第一个输出的顶点必是无后继(出度为0)的顶点,它应是拓扑序列的最后一个顶点。若希望得到的不是逆拓扑序列,同样可增加T来保存输出的顶点。若假设T是栈,并在DFSTraverse算法的开始处将T初始化,
利用DFS求拓扑序列的抽象算法可描述为:
void DFSTopSort(G,i,T){
//在DisTraverse中调用此算法,i是搜索的出发点,T是栈
int j;
visited[i]=TRUE; //访问i
for(所有i的邻接点j)//即<i,j>∈E(G)
if(!visited[j])
DFSTopSort(G,j,T);
//以上语句完全类似于DFS算法
Push(&T,i); //从i出发的搜索已完成,输出i
}
只要将深度优先遍历算法DFSTraverse中对DFS的调用改为对DFSTopSort的调用,即可求得拓扑序列T。其具体算法不难从上述抽象算法求精后得到。
若G是一个DAG,则用DFS遍历实现的拓扑排序与NonSuccFirstTopSort算法完全类似;但若C中存在有向环,则前者不能正常工作。
*************************************************************************
dfs实现拓扑排序 函数(算法竞赛入门经典)
E(u,v)
int c[maxn];
int topo[maxn],t;
bool dfs(int u)
{
c[u]=-1; //开始访问该顶点
for(int v=0;v<n;v++)
{
if(G[u][v]==1)
{
if(c[v]<0) return false; //c[v]=-1代表正在访问该定点(即递归调用dfs(u)正在帧栈中,尚未返回)
else if(!c[v] && !dfs(v)) return false; //(c[v]==0 && dfs(v)==false即当前顶点没有后即顶点时,
//开始返回 (结束))
}
}
c[u]=1; //访问结束
topo[--t]=u;
return true;
}
bool toposort()
{
t=n;
memset(c,0,sizeof(c));
for(int u=0;u<n;u++)
if(!c[u]) if(!dfs()) return false;
return ture;
}