拓扑排序与关键路径

拓扑排序

基本概念

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为弧 &lt; v , w &gt; &lt;v,w&gt; <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 &lt; v i , v k &gt; ) k ! = 0 且 &lt; v i , v k &gt; ∈ P [ k ] etv[k]=\begin{cases} 0 &amp; k=0 \\ max(etv[i]+len&lt;v_i,v_k&gt;) &amp; k!=0且&lt;v_i,v_k&gt;\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 &lt; v k , v j &gt; ) k ! = n − 1 且 &lt; v k , v j &gt; ∈ S [ k ] ltv[k]=\begin{cases} etv[n-1] &amp; k=n-1 \\ min(ltv[j]-len&lt;v_k,v_j&gt;) &amp; k!=n-1且&lt;v_k,v_j&gt;\in S[k] \\ \end{cases} ltv[k]={etv[n1]min(ltv[j]len<vk,vj>)k=n1k!=n1<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 是 由 弧 &lt; v k , v j &gt; 表 示 。 ete[i]=etv[k],活动a_i是由弧&lt;v_k,v_j&gt;表示。 ete[i]=etv[k],ai<vk,vj>
这 表 示 活 动 a i 开 始 的 最 早 时 间 应 等 于 时 间 v k 的 最 早 发 生 时 间 这表示活动a_i开始的最早时间应等于时间v_k的最早发生时间 aivk

4. l t e [ i ] = l t v [ j ] − l e n &lt; v k , v j &gt; , 活 动 a i 由 弧 &lt; v k , v j &gt; 表 示 。 lte[i]=ltv[j]-len&lt;v_k,v_j&gt;,活动a_i由弧&lt;v_k,v_j&gt;表示。 lte[i]=ltv[j]len<vk,vj>,ai<vk,vj>
则 a i 的 最 晚 开 始 时 间 要 保 证 时 间 v j 的 最 迟 发 生 时 间 不 延 后 则a_i的最晚开始时间要保证时间v_j的最迟发生时间不延后 aivj

关键路径的计算
当我们有了 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便为关键路径。 ifete[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间存在弧 &lt; v , w &gt; &lt;v,w&gt; <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,若有弧 &lt; v , w &gt; &lt;v,w&gt; <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[n1]=etv[n1]。我们在计算 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)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值