拓扑排序
基本概念
AOV网:在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示的活动网,我们称为AOV网(Activity On Vertex Network)
设
G
(
V
,
n
)
G(V,n)
G(V,n)是一个具有n个顶点的的有向图,V中顶点序列
v
1
,
v
2
,
v
3
,
.
.
.
,
v
n
v_1,v_2,v_3,...,v_n
v1,v2,v3,...,vn若满足从顶点
v
i
v_i
vi到
v
j
v_j
vj有一条路径,则在顶点序列中,顶点
v
i
v_i
vi必在
v
j
v_j
vj之前,则我们称这样的序列为拓扑序列。
拓扑排序:其实就是一个对有向图构造拓扑序列的过程。 如果顶点全部被输出,则其是不存在环的AOV网,否则此有向图有环,故不是AOV。
算法基本思想
对于无环有向图 G ( V , n ) G(V,n) G(V,n),用邻接表的结构存储图,并多增加一个变量in用于储存点data的入度:
i n in in | d a t a data data | f i r s t e d g e firstedge firstedge |
---|
AOV网的 c l a s s class class代码:
class Directed_Acyclic_Graph(object):
def __init__(self,vertices=[],edges_list=[]):
self.vertices=vertices
self.edges_list=edges_list #[[tail,head],[tail,head],...]
self.num_edges = len(edges_list) #弧的数目
self.num_vertices = len(self.vertices) #顶点数目
#邻接表Link=[[vertice,[head_num,head_num,...]],[vertice,[head_num,head_num,...]],...]
self.Link=[]
#入度表num_in
self.num_in=[0 for x in range(self.num_vertices)]
#根据输入的数据,创建邻接链表以及入度表
if self.num_vertices==0:
raise IndexError
else:
for V in vertices:
a=[]
a.append(V)
b=[]
for x in self.edges_list:
if x[0]==V:
#若边是以V为tail,则延长邻接表[vertice,[head_num,head_num,...]
b.append(self.vertices.index(x[1]))
elif x[1]==V:
#若边是以V为head,则V的入度数加1
self.num_in[vertices.index(V)]+=1
a.append(b)
self.Link.append(a)
对AOV网进行拓扑排序的基本思想是:从AOV中选择一个入度为0的顶点输出,然后删去此顶点,并删除以此顶点为tail的弧,继续重复此操作,直至输出全部顶点或者AOV中不存在入度为0的点为止。
对于上述操作可用栈 s t a c k stack stack来实现,但在 p y t h o n python python中,我们可以直接用 l i s t list list来实现栈。
我们直接阐述关于用栈来实现的思想:
先将AOV网中入度为0的顶点存入栈
s
t
a
c
k
stack
stack中,然后依次
p
o
p
pop
pop出栈中元素,每
p
o
p
pop
pop出一个元素,便修改与pop出的顶点v相关的顶点
w
w
w的入度in,即
w
w
w为弧
<
v
,
w
>
<v,w>
<v,w>的
h
e
a
d
head
head,
w
w
w的入度减1,并进行判断
w
w
w的入度
i
n
in
in是否为0,为0则将
w
w
w压入栈中。不断循环,直至将栈中元素全部输出。
拓扑排序代码:
def TopologicalOrder(G):
A=[] #A即为栈stack
for i in range(G.num_vertices): #将入度为0的顶点存入A
if G.num_in[i]==0:
A.append(G.vertices[i])
while(A):
b=A.pop() #出栈
print(b)
Index=G.vertices.index(b)
for i in G.Link[Index][1]:
G.num_in[i]-=1
if G.num_in[i]==0:
A.append(G.vertices[i])
完整代码以及测试数据:
# -*- coding: utf-8 -*-
"""
Created on Sun Mar 24 20:13:07 2019
@author: Administrator
"""
from numpy import *
class Directed_Acyclic_Graph(object):
def __init__(self,vertices=[],edges_list=[]):
self.vertices=vertices
self.edges_list=edges_list #[head,tail]
self.Link=[] #邻接表[[vertice,[head_num,head_num,...]]]
self.num_edges = len(edges_list)
self.num_vertices = len(self.vertices)
self.num_in=[0 for x in range(self.num_vertices)]
if self.num_vertices==0:
raise IndexError
else:
for V in vertices:
a=[]
a.append(V)
b=[]
for x in self.edges_list:
if x[0]==V:
b.append(self.vertices.index(x[1]))
elif x[1]==V:
self.num_in[vertices.index(V)]+=1
a.append(b)
self.Link.append(a)
def TopologicalOrder(G):
A=[]
for i in range(G.num_vertices): #将入度为0的顶点存入a
if G.num_in[i]==0:
A.append(G.vertices[i])
while(A):
b=A.pop()
print(b)
Index=G.vertices.index(b)
for i in G.Link[Index][1]:
G.num_in[i]-=1
if G.num_in[i]==0:
A.append(G.vertices[i])
vertices=['A','B','C','D','E','F','G','H','I']
edges_list=[['A','B'],['A','C'],['C','D'],['D','E'],['B','E'],['E','F'],['E','G'],['E','H'],['F','I'],['G','I'],['H','I']]
G=Directed_Acyclic_Graph(vertices,edges_list)
TopologicalOrder(G)
\
关键路径
基本概念
AOE网:在表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动持续的时间,这种有向图的边表示活动的网,我们称之为AOE网(Activity On Edge Network)
关键路径:我们把路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径,在关键路径上的活动叫关键活动。
算法基本思想
先定义几个参数:
1.事件的最早发生时间
e
t
v
(
e
a
r
l
i
e
s
t
t
i
m
e
o
f
v
e
r
t
e
x
)
etv(earliest\ time\ of\ vertex)
etv(earliest time of vertex):即顶点
v
k
v_k
vk的最早发生时间。
2.事件的最晚发生时间
l
t
v
(
l
a
t
e
s
t
t
i
m
e
o
f
v
e
r
t
e
x
)
ltv(latest\ time\ of\ vertex)
ltv(latest time of vertex):即顶点
v
k
v_k
vk的最晚发生时间,也就是每个顶点对应的事件最晚需要开始的时间,超出此时间将会延误整个工期。
3.活动最早开工时间
e
t
e
(
e
a
r
l
i
e
s
t
t
i
m
e
o
f
e
d
g
e
)
ete(earliest\ time\ of\ edge)
ete(earliest time of edge):即弧
a
i
a_i
ai最早发生时间。
4.活动最晚开工时间
l
t
e
(
l
a
t
e
s
t
t
i
m
e
o
f
e
d
g
e
)
lte(latest\ time\ of\ edge)
lte(latest time of edge):即弧
a
i
a_i
ai最晚发生时间,也就是不推迟工期的最晚开工时间。
AOE网的性质: (1)只有在某顶点所代表的事件发生后,从该顶点出发的各活动才能开始;(2)只有在进入某顶点的各活动都结束,该顶点所代表的事件才能发生。
参数上述4个参数的计算公式:
1.
e
t
v
[
k
]
=
{
0
k
=
0
m
a
x
(
e
t
v
[
i
]
+
l
e
n
<
v
i
,
v
k
>
)
k
!
=
0
且
<
v
i
,
v
k
>
∈
P
[
k
]
etv[k]=\begin{cases} 0 & k=0 \\ max(etv[i]+len<v_i,v_k>) & k!=0且<v_i,v_k>\in P[k] \\ \end{cases}
etv[k]={0max(etv[i]+len<vi,vk>)k=0k!=0且<vi,vk>∈P[k]
P
[
k
]
表
示
所
有
到
达
顶
点
v
k
的
弧
的
集
合
P[k]表示所有到达顶点v_k的弧的集合
P[k]表示所有到达顶点vk的弧的集合
2.
l
t
v
[
k
]
=
{
e
t
v
[
n
−
1
]
k
=
n
−
1
m
i
n
(
l
t
v
[
j
]
−
l
e
n
<
v
k
,
v
j
>
)
k
!
=
n
−
1
且
<
v
k
,
v
j
>
∈
S
[
k
]
ltv[k]=\begin{cases} etv[n-1] & k=n-1 \\ min(ltv[j]-len<v_k,v_j>) & k!=n-1且<v_k,v_j>\in S[k] \\ \end{cases}
ltv[k]={etv[n−1]min(ltv[j]−len<vk,vj>)k=n−1k!=n−1且<vk,vj>∈S[k]
S
[
k
]
表
示
所
有
从
顶
点
v
k
出
发
的
弧
的
集
合
S[k]表示所有从顶点v_k出发的弧的集合
S[k]表示所有从顶点vk出发的弧的集合
3.
e
t
e
[
i
]
=
e
t
v
[
k
]
,
活
动
a
i
是
由
弧
<
v
k
,
v
j
>
表
示
。
ete[i]=etv[k],活动a_i是由弧<v_k,v_j>表示。
ete[i]=etv[k],活动ai是由弧<vk,vj>表示。
这
表
示
活
动
a
i
开
始
的
最
早
时
间
应
等
于
时
间
v
k
的
最
早
发
生
时
间
这表示活动a_i开始的最早时间应等于时间v_k的最早发生时间
这表示活动ai开始的最早时间应等于时间vk的最早发生时间
4.
l
t
e
[
i
]
=
l
t
v
[
j
]
−
l
e
n
<
v
k
,
v
j
>
,
活
动
a
i
由
弧
<
v
k
,
v
j
>
表
示
。
lte[i]=ltv[j]-len<v_k,v_j>,活动a_i由弧<v_k,v_j>表示。
lte[i]=ltv[j]−len<vk,vj>,活动ai由弧<vk,vj>表示。
则
a
i
的
最
晚
开
始
时
间
要
保
证
时
间
v
j
的
最
迟
发
生
时
间
不
延
后
则a_i的最晚开始时间要保证时间v_j的最迟发生时间不延后
则ai的最晚开始时间要保证时间vj的最迟发生时间不延后
关键路径的计算
当我们有了
e
t
v
[
k
]
,
l
t
v
[
k
]
etv[k],ltv[k]
etv[k],ltv[k]后,便可以通过
e
t
v
[
k
]
,
l
t
v
[
k
]
etv[k],ltv[k]
etv[k],ltv[k]计算出
e
t
e
[
i
]
,
l
t
e
[
i
]
ete[i],lte[i]
ete[i],lte[i],然后通过比较:
i
f
:
e
t
e
[
i
]
=
=
l
t
e
[
i
]
,
则
a
i
便
为
关
键
路
径
。
if:ete[i]==lte[i],则a_i便为关键路径。
if:ete[i]==lte[i],则ai便为关键路径。最后输出关键路径即可。
关于
e
t
v
[
k
]
etv[k]
etv[k]与
l
t
v
[
k
]
ltv[k]
ltv[k]的计算,我们可以利用拓扑排序来完成。
首先是
e
t
v
[
k
]
etv[k]
etv[k],在进行拓扑排序时,我们可对入栈
s
t
a
c
k
stack
stack的每个元素行进计算其
e
t
v
[
k
]
etv[k]
etv[k]。将最先入栈的顶点,即在AOE中入度为0的顶点,赋其
e
t
v
[
k
]
=
0
etv[k]=0
etv[k]=0。在
v
v
v出栈
s
t
a
c
k
stack
stack时,可利用邻接表计算出
w
w
w的
e
t
v
etv
etv值,其中
v
v
v与
w
w
w间存在弧
<
v
,
w
>
<v,w>
<v,w>。当然,并不一定此
e
t
v
etv
etv值是
w
w
w的最终的
e
t
v
etv
etv值,但是当拓扑排序完成后,便可得到每个顶点真正的
e
t
v
etv
etv值,即取按照前面方法计算出的关于
w
w
w的
e
t
v
etv
etv值的最大值即可。
其次是
l
t
e
[
k
]
lte[k]
lte[k],在计算
e
t
v
[
k
]
etv[k]
etv[k]时,我们已经得到了AOE的拓扑排序,也是用栈保存的,设为
s
t
a
c
k
2
stack2
stack2。因为栈先进后出,故可保证对于出栈元素
v
v
v,若有弧
<
v
,
w
>
<v,w>
<v,w>,则
w
w
w必定先于
v
v
v出栈。先设栈顶元素
v
n
v_n
vn的
l
t
v
ltv
ltv值:
l
t
v
[
n
−
1
]
=
e
t
v
[
n
−
1
]
ltv[n-1]=etv[n-1]
ltv[n−1]=etv[n−1]。我们在计算
l
t
v
[
k
]
ltv[k]
ltv[k]时是需要以
v
k
v_k
vk为tail的弧的权重,以及弧的
h
e
a
d
head
head的
l
t
v
ltv
ltv值,故此可保证对于
l
t
v
[
k
]
ltv[k]
ltv[k]可通过栈
s
t
a
c
k
2
stack2
stack2的递归计算。
最后,通过已经得到的
e
t
v
[
k
]
etv[k]
etv[k]与
l
t
v
[
k
]
ltv[k]
ltv[k]计算
e
t
e
[
i
]
ete[i]
ete[i]与
l
t
e
[
i
]
lte[i]
lte[i]即可。
完整代码以及测试数据
# -*- coding: utf-8 -*-
"""
Created on Sun Mar 31 15:55:42 2019
@author: Administrator
"""
from numpy import *
class Directed_Acyclic_Graph(object):
def __init__(self,vertices=[],edges_list=[]):
self.vertices=vertices
self.edges_list=edges_list #[tail,head,wight]
self.Link=[] #邻接表[[vertice,[head,wight],[head.weight],...]...]
# self.edges_dict = {} #{(tail,head)}
self.num_edges = len(edges_list)
self.num_vertices = len(self.vertices)
self.num_in=[0 for x in range(self.num_vertices)]
if self.num_vertices==0:
raise IndexError
else:
for V in vertices:
a=[]
a.append(V)
for x in self.edges_list:
if x[0]==V:
a.append([x[1],x[2]])
elif x[1]==V:
self.num_in[vertices.index(V)]+=1
self.Link.append(a)
def TopologicalSort(G):
A=[] #用于存放入度为0的顶点
B=[] #用于存储拓扑排序
num_in=G.num_in[:]
etv=[0 for i in range(G.num_vertices)] #先初始化etv[k]=etv[0]=0
for i in range(G.num_vertices): #将入度为0的顶点存入A
if G.num_in[i]==0:
A.append(G.vertices[i])
while(A):
b=A.pop()
print(b)
B.append(b)
Index=G.vertices.index(b)
for x in G.Link[Index][1:]: #x形如[head,wight]
num_in[G.vertices.index(x[0])]-=1
if num_in[G.vertices.index(x[0])]==0:
#若入度为0,则入栈A
A.append(G.vertices[G.vertices.index(x[0])])
if etv[Index]+x[1]>etv[G.vertices.index(x[0])]:
#在for循环中求etv[k]=max(etv[i]+len<v_i,v_k>) 的过程
etv[G.vertices.index(x[0])]=etv[Index]+x[1]
#返回拓扑排序以及etv
return B,etv
def CriticalPath(G):
B,etv=TopologicalSort(G)
ltv=[etv[-1] for i in range(G.num_vertices)] #先初始化ltv[k]=ltv[n-1]=etv[n-1]
while(B):
b=B.pop()
Index=G.vertices.index(b)
for x in G.Link[Index][1:]: #x形如[tail,wight]
if ltv[G.vertices.index(x[0])]-x[1]<ltv[Index]:
#在for循环中求解ltv[k]=min(ltv[j]-len<v_k,v_j>)的过程
ltv[Index]=ltv[G.vertices.index(x[0])]-x[1]
#利用邻接表遍历AOE,然后求ete与lte,并判断是否相等,输出关键路径
for i in range(G.num_vertices):
for x in G.Link[i][1:]:
ete=etv[i]
lte=ltv[G.vertices.index(x[0])]-x[1]
if ete==lte:
print('(%s,%s):%d'%(G.vertices[i],x[0],x[1]))
vertices=['A','B','C','D','E','F','G','H','I']
edges_list=[['A','B',6],['A','C',4],['A','D',5],['B','E',1],['C','E',1],['D','F',2],['F','H',4],['E','G',9],['E','H',7],['H','I',4],['G','I',2]]
G=Directed_Acyclic_Graph(vertices,edges_list)
CriticalPath(G)