zoukankan      html  css  js  c++  java
  • 多对多关系。顶点的有穷非空集合和顶点之间边的集合构成,表示为G(V, E)。

    数据元素在线性表中叫元素、树中叫结点、图中叫顶点vertex。边无方向称为无向边,用无序偶(Vi, Vj)来表示。有向边(也称为弧)用有序偶<Vi, Vj>来表示。<弧尾,弧头>

    基础概念

    简单图:不存在顶点到自身的边,且一条边不重复出现。

    无向完全图:任意两个顶点之间都存在边的无向图。n个顶点的无向完全图有n(n-1)/2条边。

    有向完全图:任意两个顶点之间都存在两条弧的有向图。n个顶点的有向完全图有n(n-1)条边。

    稀疏图和稠密图:边或弧数小于nlogn的图为稀疏图,反之为稠密图。

    网:边或弧带权的图。

    子图:顶点和边都为子集。

    无向图顶点的度:和顶点相关联的边的数目。

    有向图顶点的入度和出度:以顶点为头的弧的数目成为入度,以顶点为尾的弧的数目成为出度。度=入度+出度。

    简单路径:顶点不重复的路径。

    连通图:两个顶点之间有路径则是连通的,如果图中任意两个顶点都连通,就是连通图。

    连通分量:无向图中的极大连通子图称为连通分量。

    有向图中,如果任意一对顶点之间都存在路径,则称为强连通图。

    有向图中的极大强连通子图称为有向图的强连通分量。

    连通图的生成树:一个极小的连通子图,含有图中全部的n个顶点,但只有足以构成一棵树的n-1条边。

    如果一个有向图恰有一个顶点入度为0,其余顶点的入度均为1,那么就是一颗有向树。

    图的存储结构

    无法以数据元素在内存中的物理位置来表示元素之间的关系(内存物理关系是线性的,图的关系是平面的)。

    如果各个顶点度数相差太大,纯粹用多种链表导致无法想象的浪费。

    五种不同的存储结构,特别是邻接矩阵和邻接表。

    1. 邻接矩阵

    一维数组存储顶点(因为顶点不分大小主次);边或弧用二维数组表示。无向图的邻接矩阵是对称的,浪费了一半空间。

           

    有向图的邻接矩阵不一定对称,顶点的入度是对应列不为0的个数之和,出度是对应行的。没有浪费空间,因为一定要区分弧的方向。

           

    网的邻接矩阵,对应位置的取值是对应弧的权值,不存在的弧可以用一个计算机允许的、大于所有边上权值的值来表示。

    2. 邻接表

    边数相对于顶点较少的图,用邻接矩阵也很浪费空间。这时可以用邻接表,数组+链表。还是一维数组,但存的是顶点和指向该节点的第一个邻接点的指针,每个顶点的所有邻接点构成一个线性表,由于邻接点的个数不定,选择用单链表来存储。

             

    对于有向图,顶点当弧尾建立邻接表,得到的是每个顶点的出度。(入度的话是顶点当弧头;逆邻接表)。

          

    对于网,可以在边表结点的定义中再加一个数据域来存储权。

          

    3. 十字链表

    针对有向图的优化,结合邻接表和逆邻接表。定义顶点表结点结构包括数据、指向第一个入的邻接点的指针、指向第一个出的邻接点的指针:

    边表结点结构:,tailVex弧尾顶点下标、headVex弧头顶点下标。也就是说一个结点中存的是两条弧。

         

    既容易找到以Vi为尾的弧,也容易找到以Vi为头的弧。

    4.邻接多重表

      无向图的应用中,如果关注的是边的操作而不是顶点,那么邻接表的操作就相对麻烦。仿照十字链表的做法对边表结构进行扩充:,iVex和 jVex是与某条边依附的两个顶点在顶点表中的下标,iLink指向依附顶点iVex的下一条边,jLink指向依附顶点jVex的下一条边。也就是说,边表存放的是一条边而不是一个顶点。

    5.边集数组

      两个一维数组,一个存储顶点信息,一个存储边的信息,边数组每个数据元素由一条边的起点下标、终点下标和权组成。

    图的遍历

    1. 深度优先遍历(dfs):更符合计算机的计算逻辑。

    规定一个右手原则,没有碰到重复顶点的情况下一直向右,每过一个顶点就标记一次。重复顶点了就退回上一步,找没有重复的顶点。推到最开始的顶点了,遍历就结束。其实就是一个递归过程。

    dfs 搜索方式,每次都一条路走到头,再回溯看看有没有其他的路可走。

        

     1 visited = set()
     2 
     3 def DFS(node, visited):
     4      visited.add(node)
     5     
     6     # process current node
     7     ...
     8 
     9     for next_node in node.children():
    10         if not next_node in visited:
    11             DFS(next_node, visited)
    View Code  

    非递归写法:

     1 def DFS(tree):
     2     if tree.root is None:
     3         return []
     4     
     5     visited, stack = [], [tree.root]
     6     
     7     while stack:
     8         node = stack.pop()
     9         visited.add(node)
    10         
    11         process(node)
    12         nodes = generate_related_nodes(node)
    13         stack.push(nodes)
    14     
    15     # other processing work
    16     ...
    View Code

    马踏棋盘,遍历8*8的棋盘,其中马的走法规定为只有以下八个方向

    n >= 5且为偶数的情况下,以任意点作起始点都有解。

    哈密尔顿路径:经过图中每个顶点且只经过一次的路径。

     1 def ChessTraval(n=5, entry=(2,2)):
     2     """棋盘大小n,出发点entry"""
     3     
     4     def could_place(k):  
     5         # 检测边界和是否放过马
     6         if (0<=res[k][0]<n) and (0<=res[k][1]<n) and (res[k] not in set(res[:k])):
     7             return True
     8         return False
     9     
    10     def depthTraval(k):
    11         if k == n**2:  # 所有位置放完,得到一组解
    12             output.append(res)
    13         else:  # 没放完的话就继续找res[k]的位置
    14             for s in states:   # 遍历point可能跳转的8个状态
    15                 res[k] = (res[k-1][0]+s[0], res[k-1][1]+s[1])
    16                 if could_place(k):  # 如果当前位置能放,去找后面所有可能的放法;不能放就跳过;回溯通过直接对res[k]赋值来实现
    17                     depthTraval(k+1)
    18                 
    19     states = [(-1, 2), (1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1)]
    20     res = [None]*(n**2) # 一组解
    21     output = []  # 所有解
    22     res[0] = entry
    23     depthTraval(1)
    24     return output
    View Code

    2. 广度优先遍历(bfs):直观且符合人类习惯,层层递进式的地毯式搜索。

    用队列实现,从一个点A开始,A入队,A出队时按右手原则(从右到左)遍历所有和A相连的点(依次入队),再出队队中元素,每个元素出队时就将与其相连的点入队,已经遍历过的结点给一个标志之后即使再遇到也不入队。

    bfs 搜索方式,以二叉树为例:

     1 def BFS(graph, start, end):
     2     queue = []
     3     queue.append([start])  # 队列
     4     visited.add(start)  # 集合
     5     
     6     while queue:
     7         node = queue.pop()
     8         visited.add(node)
     9         
    10         process(node)
    11         nodes = generate_related_nodes(node)  # 找node的后继节点,并判断是否被遍历过
    12         queue.push(nodes)
    13     
    14     # other processing work
    15     ...
    View Code

    3. 剪枝

    只保留最好的(非常确定)或数条比较好(不太确定当前最优分支是否是全局最优分支,优先搜索当前最优的分支)的分支进行后续搜索。

    最小生成树

    给定无向图,以最小成本的n-1条带权连线连接所有n个顶点。

    1. prim算法:

      1. 任意选出一个点作为初始顶点,标记为visit,计算所有与之相连接的点的距离,选择距离最短的点,标记visit。

      2. 重复以下操作,直到所有点都被标记为visit

        在剩下的点中,计算与已标记的所有visit点距离最小的点(动态排序,取最小值),标记为visit,加入最小生成树。

     1 from collections import defaultdict
     2 from heapq import *
     3 
     4 def prim(vertices, edges):
     5     """顶点vertices [v1, v2, ...]
     6     带权无向边edges [(v1, v2, weight) ... ]
     7     """
     8     adjecent_vertex = defaultdict(list)  # defaultdict(list)必须以list做为变量
     9     
    10     # 构建邻接矩阵,key是顶点,value是与key相连的点及边的权值
    11     for v1, v2, length in edges:
    12         adjecent_vertex[v1].append((length, v1, v2))
    13         adjecent_vertex[v2].append((length, v2, v1))
    14 #     defaultdict(<type 'list'>, {'A': [(7, 'A', 'B'), (5, 'A', 'D')], 
    15 #                                 'C': [(8, 'C', 'B'), (5, 'C', 'E')], 
    16 #                                 'B': [(7, 'B', 'A'), (8, 'B', 'C'), (9, 'B', 'D'), (7, 'B', 'E')], 
    17 #                                 'E': [(7, 'E', 'B'), (5, 'E', 'C'), (15, 'E', 'D'), (8, 'E', 'F'), (9, 'E', 'G')], 
    18 #                                 'D': [(5, 'D', 'A'), (9, 'D', 'B'), (15, 'D', 'E'), (6, 'D', 'F')], 
    19 #                                 'G': [(9, 'G', 'E'), (11, 'G', 'F')], 
    20 #                                 'F': [(6, 'F', 'D'), (8, 'F', 'E'), (11, 'F', 'G')]})
    21     
    22     mst = []  # 存储最小生成树
    23     chosed = set(vertices[0])  # 先选任意一个顶点作为起始,例如 A
    24     adjecent_vertices_edges = adjecent_vertex[vertices[0]]  # 与选中顶点相连的边的list [(7, 'A', 'B'), (5, 'A', 'D')]
    25     heapify(adjecent_vertices_edges)  # 加入堆中,之后能够用heapqpop动态取出最小值
    26     
    27     while adjecent_vertices_edges:  # 直到所有点都标记为visit为止
    28         w, v1, v2 = heappop(adjecent_vertices_edges)  # 取出与A相连的权最小边 (5, 'A', 'D')
    29         if v2 not in chosed:  # 如果D不在标记visit点中
    30             chosed.add(v2)  # 标记D
    31             mst.append((v1, v2, w))  # 这条边加入最小生成树
    32             for next_vertex in adjecent_vertex[v2]:  # 再找所有与D相连的边,如果没有标记过就加入堆。因为与A相连的边已经加入过堆了
    33                 if next_vertex[2] not in chosed:
    34                     heappush(adjecent_vertices_edges, next_vertex)
    35     return mst
    36 
    37 # test
    38 vertices = list("ABCDEFG")
    39 edges = [ ("A", "B", 7), ("A", "D", 5),
    40           ("B", "C", 8), ("B", "D", 9), 
    41           ("B", "E", 7), ("C", "E", 5),
    42           ("D", "E", 15), ("D", "F", 6),
    43           ("E", "F", 8), ("E", "G", 9),
    44           ("F", "G", 11)]
    45 print("edges:",edges)
    46 print("prim:", prim(vertices, edges))
    View Code

    2. kruskal算法:

      1. 将每个顶点放入自身的数据集合中,得到n个集合。

      2. 按照权值的升序来选择边。每选择一条边,判断边的两个顶点是否在不同的集合中(并查集)。如果是,将此边加入最小生成树的集合中,同时将集合中包含这两个顶点的联合体取出;如果不是,就继续选择下一条边。

      3. 重复2直到所有边都检查过。

     1 X = dict()  # 以全局变量X定义节点集合,即类似{'A':'A','B':'B','C':'C','D':'D'},如果A、B两点联通,则会更改为{'A':'B','B':'B",...},即任何两点联通之后,两点的值value将相同。
     2 R = dict()  # 各点的初始等级均为0,如果被做为连接的的末端,则增加1
     3 
     4 
     5 # 节点的联通分量
     6 def find(point):
     7     if X[point] != point:
     8         X[point] = find(X[point])
     9     return X[point]
    10 
    11 # 连接两个分量(节点)
    12 def merge(point1,point2):
    13     r1 = find(point1)  # v1的连通末端
    14     r2 = find(point2)  # v2的连通末端
    15     if r1 != r2:   # 如果连通末端不同,说明v1、v2不连通,不连通的情况下就令其连通
    16         if R[r1] > R[r2]:  # 如果v1的末端r1的连接等级高,连通后记为r2:r1
    17             X[r2] = r1  
    18         else:  # 如果r1没有更高,记为r1:r2
    19             X[r1] = r2  
    20             if R[r1] == R[r2]: # 如果r1、r2连接等级相同,选择一个作为连接末端,连接等级加1
    21                 R[r2] += 1
    22                 
    23 def kruskal(vertices, edges):
    24     # 设置 X、R 的初始值
    25     for vertex in vertices:
    26         X[vertex] = vertex
    27         R[vertex] = 0  # 连接等级都为0
    28         
    29     mst = set()
    30     edges = list(edges)
    31     edges.sort()  # 按照权值升序来选择边
    32         
    33     for edge in edges:  # 直到所有边都检查过
    34         w, v1, v2 = edge
    35         if find(v1) != find(v2):  # 判断边的顶点是否连通,如果不连通
    36             merge(v1, v2)  # merge顶点,将其连通
    37             mst.add(edge)  # 将此边加入最小生成树
    38     return mst
    39 
    40 # test
    41 vertices =  list('ABCDEF')
    42 edges = set([
    43         (1, 'A', 'B'),
    44         (5, 'A', 'C'),
    45         (3, 'A', 'D'),
    46         (4, 'B', 'C'),
    47         (2, 'B', 'D'),
    48         (1, 'C', 'D'),
    49         ])
    50 result = kruskal(vertices, edges)
    51 print('edges:', edges)
    52 print('kruskal:', result)
    View Code

    最短路径

    1. dijkstra算法:非负权有向图的单源最短路径问题。

      1. 设置两个顶点的集合T和S,一个辅助向量D。S为已找到最短路径的顶点的集合,初始时,集合S中只有一个顶点,即源点v0;T是当前还未找到最短路径的顶点的集合;D的每个分量D[i] 表示当前找到的从源点v0到vi的最短路径长度(weight),初始时若v0到vi有弧则D[i]为弧上权值,否则为∞。

      2. 在集合T中遍历找出当前长度最短的一条最短路径(v0,…,vk),从而将vk加入到顶点集合S中。然后检查新加入的顶点是否可以到达其他顶点,并且检查通过该顶点到达其他点的路径长度是否比从V0直接到达其他点更短。如果是,修改源点v0到T中各顶点的最短路径长度。

      3. 重复2,直到所有的顶点都加入到集合S中。

    长度为D[k] = Min{D[i] | vi in V}的路径就是从v0出发最短的一条路径 (v0, vi)。下一条长度次短的最短路径(v0到vj),要么直接是弧(v0, vj)、长度为弧上权值,要么是(v0, vk, vj)、长度为D[k] + (vk, vj)弧上权值。

     1 from collections import defaultdict
     2 from heapq import *
     3 
     4 def dijkstra(edges, f, t):
     5     """有向图的边,源点,目标点"""
     6     g = defaultdict(list)  # 构建邻接矩阵
     7     for l,r,c in edges:
     8         g[l].append((c,r))
     9 #     {'A': [(7, 'B'), (5, 'D')], 
    10 #      'B': [(8, 'C'), (9, 'D'), (7, 'E')], 
    11 #      'C': [(5, 'E')], 
    12 #      'D': [(15, 'E'), (6, 'F')], 
    13 #      'E': [(8, 'F'), (9, 'G')], 
    14 #      'F': [(11, 'G')]})
    15     
    16     q = [(0, f, ())]  # (源点到当前点的最短距离,当前点,对应的路径)
    17     seen = set()   # 集合s,已经找到最短路径的顶点的集合
    18     mins = {}  # 源点到T中各顶点最短路径长度,辅助向量D
    19     while q:
    20         cost, vk, path = heappop(q)  # 源点到当前T中顶点的最短距离cost,对应顶点是vk
    21         if vk not in seen:  # 确保vk不在S中
    22             seen.add(vk)  # vk加入S中,也即退出T
    23             path = (vk, path)  # 记录源点到vk经过的路径
    24             if vk == t:  # 如果vk是目标点的话直接返回即可,就是最短距离和对应路径
    25                 return (cost, path)
    26 
    27             # 如果vk不是目标点,检查vk是否可以到达其他顶点
    28             for c, vj in g.get(vk, ()):  # vk出度为0时返回()
    29                 if vj not in seen:  # 只需要检查vk能到达的顶点中所有属于T的vj
    30                     prev = mins.get(vj, None)  # D[j],源点到vj的最短路径,不存在时返回None
    31                     next_ = cost + c  # D[vk] + (vk, vj)弧长
    32                     if prev is None or next_ < prev:  # 如果路径v0-...-vk-vj权值比路径v0-vj更小
    33                         mins[vj] = next_  # 更新源点到T中最短距离
    34                         heappush(q, (next_, vj, path))  # 加入堆
    35 
    36     return float("inf")
    37 
    38 # test
    39 edges = [
    40     ("A", "B", 7),
    41     ("A", "D", 5),
    42     ("B", "C", 8),
    43     ("B", "D", 9),
    44     ("B", "E", 7),
    45     ("C", "E", 5),
    46     ("D", "E", 15),
    47     ("D", "F", 6),
    48     ("E", "F", 8),
    49     ("E", "G", 9),
    50     ("F", "G", 11)
    51 ]
    52 
    53 print("=== Dijkstra ===")
    54 print(edges)
    55 print("A -> E:")
    56 print(dijkstra(edges, "A", "E"))
    57 print("F -> G:")
    58 print(dijkstra(edges, "F", "G"))
    View Code

    2. floyd算法:解决有向图或负权(但不可存在负权回路)有向图任意两点间的最短路径。所有顶点到所有顶点到最短路径。

      1. 引入两个矩阵,矩阵S中的元素s[i][j]表示顶点i(第i个顶点)到顶点j(第j个顶点)的距离。矩阵P中的元素p[i][j],表示顶点i到顶点j经过了p[i][j]记录的值所表示的顶点。

      2. 假设图G中顶点个数为N,则需要对矩阵S和矩阵P进行N次更新。初始时,邻接矩阵S中s[i][j]表示为顶点i到顶点j的权值,如果i和j不相邻,则s[i][j]=∞;矩阵P的值为顶点p[i][j]的j的值。

      3. 对矩阵D进行N次更新:

       for k = 0, 1, ..., N-1,如果s[i][j] > s[i][k]+s[k][j],则更新s[i][k]为s[i][k]+s[k][j]”,且更新p[i][j]=p[i][k-1]

     1 def Floyd(dis):
     2     #min (Dis(i,j) , Dis(i,k) + Dis(k,j) )
     3     nums_vertex = len(dis[0])
     4     for k in range(nums_vertex):
     5         for i in range(nums_vertex):
     6             for j in range(nums_vertex):
     7                 if dis[i][j] > dis[i][k] + dis[k][j]:
     8                     dis[i][j] = dis[i][k] + dis[k][j]
     9     return dis
    10 
    11 # test
    12 inf = float('inf')
    13 matrix_distance = [[0,1,12,inf,inf,inf],
    14                    [inf,0,9,3,inf,inf],
    15                    [inf,inf,0,inf,5,inf],
    16                    [inf,inf,4,0,13,15],
    17                    [inf,inf,inf,inf,0,4],
    18                    [inf,inf,inf,inf,inf,0]]
    19 
    20 print(Floyd(matrix_distance))
    View Code

    总结:图的DFS、BFS、Dijkstra、Floyd、Prim、Kruskal  https://zhuanlan.zhihu.com/p/61628249

    拓扑排序

    无环有向图:DAG。小的工程或阶段称为“活动”。在一个表示工程的有向图无环图中,顶点表示活动,弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网称为AOV网,表示活动之间存在某种制约关系。AOV网无回路。

    拓扑序列:有向图顶点v1,...vn满足:vi到vj有一条路径,则顶点序列中vi必在vj之前。(只能前面的点指向后面的点)

    拓扑排序:对一个有向图构造拓扑序列的过程。一个有向无环图可以有一个或多个拓扑排序序列。

    对AOV网进行拓扑排序:

      1. 选择一个没有前驱的顶点(入度为0)并且输出它。

      2. 从网中删去该顶点,并且删去从该顶点出发的全部有向边。

      3. 重复12直到网中不再存在没有前驱的顶点为止。

     1 def topsort(Graph):
     2     in_degrees = dict((u, 0) for u in Graph)
     3     for u in G:
     4         for v in G[u]:
     5             in_degrees[v] += 1  # 每一个节点的入度
     6     Q = [u for u in G if in_degrees[u] == 0]  # 入度为0的顶点
     7     S = []  # 拓扑序列
     8     while Q:
     9         u = Q.pop()  # 默认从最后一个入度为0的顶点开始移除
    10         S.append(u)  # 加入到拓扑序列中
    11         for v in G[u]:  # 删去顶点u出发的有向边
    12             in_degrees[v] -= 1 # 并移除其指向
    13             if in_degrees[v] == 0:
    14                 Q.append(v)
    15     return S
    16 
    17 # test 
    18 Graph = {'a':'bf',
    19          'b':'cdf',
    20          'c':'d',
    21          'd':'ef',
    22          'e':'f',
    23          'f':''}
    24 print(topsort(Graph))
    View Code

      

    关键路径

    AOE网:在一个表示工程的带权有向图中,顶点表示事件,有向边表示活动,边上权值表示活动持续时间。

    源点:入度为0;汇点:出度为0。

    关键路径:从源点到汇点距离最长的路径。一个工程可能会有多条关键路径。

    缩短工程工期的核心就是缩短关键路径。 

    AOV和AOE的区别:

      1. AOV用顶点表示活动的网,描述活动之间的制约关系。

      2. AOE用边表示活动的网,边上的权值表示活动持续的时间。

      3. AOE 是建立在子过程之间的制约关系没有矛盾的基础之上,再来分析整个过程需要的时间。

    事件最早开始时间etv:顶点Vi最早发生的时间。(从前往后推,v0到vi的最长路径长度)

    事件最晚开始时间ltv:顶点Vi最晚发生的时间,超出则会延误整个工期。(从后往前推,不影响工期的最晚开工时间)

    活动的最早开始时间ete:边Eg最早发生时间。(若以vi为弧尾,则ete就等于vi的etv)

    活动的最晚开始时间lte:边Eg最晚发生时间。不推迟工期的最晚开工时间。

    只需要确认每个顶点的最早开始时间和最晚开始时间,判断它们的时间差,如果没有时间差就是关键路径。

    1. 输入顶点数和边数,已经各个弧的信息建立图

    2. 从源点v1出发,按照拓扑序列往前求各个顶点的最早发生时间。如果得到的拓扑序列个数小于网的顶点数n,说明建立的图有环,无关键路径,直接结束。

    3. 从终点vn出发,按逆拓扑序列,往后求其他顶点最晚发生时间。

    4. 若etv等于ltv,说明是关键活动。

     1 def topsort(graph):
     2     vnum = len(graph)  # 顶点数
     3     indegree = dict((u, 0) for u in range(vnum))
     4     topseq = []
     5     
     6     for x in range(vnum):
     7         for y in range(vnum):
     8             if graph[x][y] > 0:
     9                 indegree[y] += 1  # 入度表
    10                 
    11     Q = [y for y in range(vnum) if indegree[y] == 0]
    12     
    13     while Q:
    14         u = Q.pop()
    15         topseq.append(u)
    16         for y in range(vnum):
    17             if graph[u][y] > 0:
    18                 indegree[y] -= 1
    19                 if indegree[y] == 0:
    20                     Q.append(y)
    21     return topseq
    22 
    23 def critcal_path(graph):
    24     def events_earliest_time(graph, topseq):  # 计算事件最早发生事件
    25         vnum = len(graph)
    26         ee = [0]*vnum  # v0到vi的最大路径长度
    27         for i in topseq:
    28             for j in range(vnum):
    29                 if graph[i][j] != 0:
    30                     if ee[i] + graph[i][j] > ee[j]:  # ee[i]表示v0到vi的最长路径
    31                         ee[j] = ee[i] + graph[i][j]  # 如果v0-...-vi-vj的路径长度大于v0-...-vj,更新ee[j]
    32         return ee
    33     
    34     
    35     def events_latest_time(graph, topseq, eelast):  # 计算时间最晚发生时间
    36         vnum = len(graph)
    37         le = [eelast]*vnum  # 各顶点的最晚发生时间,初始化为汇点的最早发生时间
    38         for k in range(vnum-2, -1, -1):
    39             i = topseq[k]
    40             for j in range(vnum):
    41                 if graph[i][j] != 0:
    42                     if le[i] + graph[i][j] > le[j]:  # 如果vi最晚时间+ij工期超过了vj最晚时间,说明耽误了工期,更新vi最晚时间
    43                         le[i] = le[j] - graph[i][j]
    44         return le
    45     
    46     def crt_road(graph, ee, le):
    47         crt = []
    48         # 可能不只一条关键路径,只遍历ee等于le的顶点不能完全确定路径
    49         for i in range(vnum):
    50             for j in range(vnum):
    51                 if graph[i][j] > 0:
    52                     if ee[i] == le[j]-graph[i][j]:  # ee[i] == le[i]
    53                         crt.append((i, j, ee[i]))
    54         return crt 
    55     
    56     vnum = len(graph)
    57     topseq = topsort(graph)
    58     if (not topseq) or len(topseq)<vnum:
    59         return None
    60     ee = events_earliest_time(graph, topseq)
    61     le = events_latest_time(graph, topseq, ee[-1])
    62     return crt_road(graph, ee, le)
    63     
    64 # test
    65 graph = [[0,7,13,8,0,0,0,0,0],
    66          [0,0,4,0,0,14,0,0,0],
    67          [0,0,0,0,5,0,8,12,0],
    68          [0,0,0,0,13,0,0,10,0],
    69          [0,0,0,0,0,7,3,0,0],
    70          [0,0,0,0,0,0,0,0,5],
    71          [0,0,0,0,0,0,0,0,7],
    72          [0,0,0,0,0,0,0,0,8],
    73          [0,0,0,0,0,0,0,0,0]]
    74 print(topsort(graph))
    75 print(critcal_path(graph))
    View Code
  • 相关阅读:
    List<Map>中根据某个属性去重,不重复的值则覆盖null
    Jenkins的Master-Slave分布式构建
    Jenkins+Docker+SpringCloud微服务持续集成(集群版)
    Jenkins+Docker+SpringCloud微服务持续集成(单机版)
    Jenkins持续集成实战
    Python面向对象总结
    Python监控Redis脚本
    SnmpWalk获取服务器状态信息
    xclarity
    CNware
  • 原文地址:https://www.cnblogs.com/chaojunwang-ml/p/11257250.html
Copyright © 2011-2022 走看看