拓扑排序与关键路径

拓扑排序

基本概念

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_ivivjv_jvj有一条路径,则在顶点序列中,顶点viv_ivi必在vjv_jvj之前,则我们称这样的序列为拓扑序列。
拓扑排序:其实就是一个对有向图构造拓扑序列的过程。 如果顶点全部被输出,则其是不存在环的AOV网,否则此有向图有环,故不是AOV。

算法基本思想

对于无环有向图G(V,n)G(V,n)G(V,n),用邻接表的结构存储图,并多增加一个变量in用于储存点data的入度:

ininindatadatadatafirstedgefirstedgefirstedge

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

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为弧&lt;v,w&gt;&lt;v,w&gt;<v,w>headheadheadwww的入度减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&lt;vi,vk&gt;)k!=0且&lt;vi,vk&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]表示所有到达顶点vk的弧的集合P[k]表示所有到达顶点v_k的弧的集合P[k]vk

2.ltv[k]={etv[n−1]k=n−1min(ltv[j]−len&lt;vk,vj&gt;)k!=n−1且&lt;vk,vj&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]表示所有从顶点vk出发的弧的集合S[k]表示所有从顶点v_k出发的弧的集合S[k]vk

3.ete[i]=etv[k],活动ai是由弧&lt;vk,vj&gt;表示。ete[i]=etv[k],活动a_i是由弧&lt;v_k,v_j&gt;表示。ete[i]=etv[k],ai<vk,vj>
这表示活动ai开始的最早时间应等于时间vk的最早发生时间这表示活动a_i开始的最早时间应等于时间v_k的最早发生时间aivk

4.lte[i]=ltv[j]−len&lt;vk,vj&gt;,活动ai由弧&lt;vk,vj&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>
则ai的最晚开始时间要保证时间vj的最迟发生时间不延后则a_i的最晚开始时间要保证时间v_j的最迟发生时间不延后aivj

关键路径的计算
当我们有了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便为关键路径。ifete[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时,可利用邻接表计算出wwwetvetvetv值,其中vvvwww间存在弧&lt;v,w&gt;&lt;v,w&gt;<v,w>。当然,并不一定此etvetvetv值是www的最终的etvetvetv值,但是当拓扑排序完成后,便可得到每个顶点真正的etvetvetv值,即取按照前面方法计算出的关于wwwetvetvetv值的最大值即可。
其次是lte[k]lte[k]lte[k],在计算etv[k]etv[k]etv[k]时,我们已经得到了AOE的拓扑排序,也是用栈保存的,设为stack2stack2stack2。因为栈先进后出,故可保证对于出栈元素vvv,若有弧&lt;v,w&gt;&lt;v,w&gt;<v,w>,则www必定先于vvv出栈。先设栈顶元素vnv_nvnltvltvltv值:ltv[n−1]=etv[n−1]ltv[n-1]=etv[n-1]ltv[n1]=etv[n1]。我们在计算ltv[k]ltv[k]ltv[k]时是需要以vkv_kvk为tail的弧的权重,以及弧的headheadheadltvltvltv值,故此可保证对于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)

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值