拓扑排序
基本概念
AOV网:在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示的活动网,我们称为AOV网(Activity On Vertex Network)
设G(V,n)G(V,n)G(V,n)是一个具有n个顶点的的有向图,V中顶点序列v1,v2,v3,...,vnv_1,v_2,v_3,...,v_nv1,v2,v3,...,vn若满足从顶点viv_ivi到vjv_jvj有一条路径,则在顶点序列中,顶点viv_ivi必在vjv_jvj之前,则我们称这样的序列为拓扑序列。
拓扑排序:其实就是一个对有向图构造拓扑序列的过程。 如果顶点全部被输出,则其是不存在环的AOV网,否则此有向图有环,故不是AOV。
算法基本思想
对于无环有向图G(V,n)G(V,n)G(V,n),用邻接表的结构存储图,并多增加一个变量in用于储存点data的入度:
ininin | datadatadata | firstedgefirstedgefirstedge |
---|
AOV网的classclassclass代码:
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的点为止。
对于上述操作可用栈stackstackstack来实现,但在pythonpythonpython中,我们可以直接用listlistlist来实现栈。
我们直接阐述关于用栈来实现的思想:
先将AOV网中入度为0的顶点存入栈stackstackstack中,然后依次poppoppop出栈中元素,每poppoppop出一个元素,便修改与pop出的顶点v相关的顶点www的入度in,即www为弧<v,w><v,w><v,w>的headheadhead,www的入度减1,并进行判断www的入度ininin是否为0,为0则将www压入栈中。不断循环,直至将栈中元素全部输出。
拓扑排序代码:
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.事件的最早发生时间etv(earliest time of vertex)etv(earliest\ time\ of\ vertex)etv(earliest time of vertex):即顶点vkv_kvk的最早发生时间。
2.事件的最晚发生时间ltv(latest time of vertex)ltv(latest\ time\ of\ vertex)ltv(latest time of vertex):即顶点vkv_kvk的最晚发生时间,也就是每个顶点对应的事件最晚需要开始的时间,超出此时间将会延误整个工期。
3.活动最早开工时间ete(earliest time of edge)ete(earliest\ time\ of\ edge)ete(earliest time of edge):即弧aia_iai最早发生时间。
4.活动最晚开工时间lte(latest time of edge)lte(latest\ time\ of\ edge)lte(latest time of edge):即弧aia_iai最晚发生时间,也就是不推迟工期的最晚开工时间。
AOE网的性质: (1)只有在某顶点所代表的事件发生后,从该顶点出发的各活动才能开始;(2)只有在进入某顶点的各活动都结束,该顶点所代表的事件才能发生。
参数上述4个参数的计算公式:
1.etv[k]={0k=0max(etv[i]+len<vi,vk>)k!=0且<vi,vk>∈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]表示所有到达顶点vk的弧的集合P[k]表示所有到达顶点v_k的弧的集合P[k]表示所有到达顶点vk的弧的集合
2.ltv[k]={etv[n−1]k=n−1min(ltv[j]−len<vk,vj>)k!=n−1且<vk,vj>∈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]表示所有从顶点vk出发的弧的集合S[k]表示所有从顶点v_k出发的弧的集合S[k]表示所有从顶点vk出发的弧的集合
3.ete[i]=etv[k],活动ai是由弧<vk,vj>表示。ete[i]=etv[k],活动a_i是由弧<v_k,v_j>表示。ete[i]=etv[k],活动ai是由弧<vk,vj>表示。
这表示活动ai开始的最早时间应等于时间vk的最早发生时间这表示活动a_i开始的最早时间应等于时间v_k的最早发生时间这表示活动ai开始的最早时间应等于时间vk的最早发生时间
4.lte[i]=ltv[j]−len<vk,vj>,活动ai由弧<vk,vj>表示。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>表示。
则ai的最晚开始时间要保证时间vj的最迟发生时间不延后则a_i的最晚开始时间要保证时间v_j的最迟发生时间不延后则ai的最晚开始时间要保证时间vj的最迟发生时间不延后
关键路径的计算
当我们有了etv[k],ltv[k]etv[k],ltv[k]etv[k],ltv[k]后,便可以通过etv[k],ltv[k]etv[k],ltv[k]etv[k],ltv[k]计算出ete[i],lte[i]ete[i],lte[i]ete[i],lte[i],然后通过比较:if:ete[i]==lte[i],则ai便为关键路径。if:ete[i]==lte[i],则a_i便为关键路径。if:ete[i]==lte[i],则ai便为关键路径。最后输出关键路径即可。
关于etv[k]etv[k]etv[k]与ltv[k]ltv[k]ltv[k]的计算,我们可以利用拓扑排序来完成。
首先是etv[k]etv[k]etv[k],在进行拓扑排序时,我们可对入栈stackstackstack的每个元素行进计算其etv[k]etv[k]etv[k]。将最先入栈的顶点,即在AOE中入度为0的顶点,赋其etv[k]=0etv[k]=0etv[k]=0。在vvv出栈stackstackstack时,可利用邻接表计算出www的etvetvetv值,其中vvv与www间存在弧<v,w><v,w><v,w>。当然,并不一定此etvetvetv值是www的最终的etvetvetv值,但是当拓扑排序完成后,便可得到每个顶点真正的etvetvetv值,即取按照前面方法计算出的关于www的etvetvetv值的最大值即可。
其次是lte[k]lte[k]lte[k],在计算etv[k]etv[k]etv[k]时,我们已经得到了AOE的拓扑排序,也是用栈保存的,设为stack2stack2stack2。因为栈先进后出,故可保证对于出栈元素vvv,若有弧<v,w><v,w><v,w>,则www必定先于vvv出栈。先设栈顶元素vnv_nvn的ltvltvltv值:ltv[n−1]=etv[n−1]ltv[n-1]=etv[n-1]ltv[n−1]=etv[n−1]。我们在计算ltv[k]ltv[k]ltv[k]时是需要以vkv_kvk为tail的弧的权重,以及弧的headheadhead的ltvltvltv值,故此可保证对于ltv[k]ltv[k]ltv[k]可通过栈stack2stack2stack2的递归计算。
最后,通过已经得到的etv[k]etv[k]etv[k]与ltv[k]ltv[k]ltv[k]计算ete[i]ete[i]ete[i]与lte[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)