def span_forest(graph): vnum = graph.get_num() DFS_seq = [None] * vnum #递归主体 def clfs(graph, v): nonlocal DFS_seq for u, w in graph.out_edge(v): #遍历节点v的所有的临接边 if DFS_seq[u] is None: #寻找生成树不包含的临接顶点 DFS_seq[u] = (v, w) #将u加入到生成树的点集合中 clfs(graph, u) #然后以u作为下次递归的起点,寻找u的临接点,找到没有包含 #在生成树中的临接点,然后将其加入生成树,依次递归。 #选择生成树的起点,因为有可能某个起点并不是根 for i in range(vnum): if DFS_seq[i] is None: DFS_seq[i] = (i, 0) clfs(graph, i) #检查是否有孤立点,即加入了两个起点其将所有的点检索完毕 counter = 0 for x in DFS_seq: if x[1] == 0: counter += 1 if counter > 1: raise ValueError('There is a islanddot') return DFS_seq
def toposort(graph): vnum = graph.get_num() #取顶点数值 indegree, toposeq = [0] * vnum, [] #建立入度表,其中度为0的顶点串成一串,拓扑序列表,初始为空表 zerov = -1 #第一个入也是相当于栈底入度为0的顶点的元素,代表往下没有入度为0的顶点了,它是第一个 for vi in range(vnum): for j, w in graph.out_edge(vi): #遍历所有顶点的临接边,然后做出所有顶点的入度表 if w != 0: #该条件排除对角线上自身为入度的干扰 indegree[j] += 1 for vi in range(vnum): #找到入度为0的顶点,然后将他们串成串,具体的是:第一个入度为0的点元素为-1,栈底,更新zerov if indegree[vi] == 0: #第二个入度为0的点的元素是上一个的顶点的序号zerov,循环。因此最后出现的为0的顶点为栈顶 indegree[ vi] = zerov #此时zerov指向该顶点的序号,然后每个indegree[vi]的元素都是下一个入度为0的顶点的序号 zerov = vi for n in range(vnum): #主循环 if zerov == -1: #没有入度为0的顶点,即zerov没有更新,那么不存在拓扑排序 return False vi = zerov #保存zerov,即第一个入度为0的顶点 zerov = indegree[ zerov] #第一个入度为0的顶点出栈(从图中取下,加入拓扑序),此时栈顶应该指向下一个入度为0的顶点的序号indegree[zerov] toposeq.append(vi) #将弹出的第一个入度为0的顶点加入拓扑序 for v, w in graph.out_edge(vi): #由于将顶点弹出,那么与该顶点相连的所有临接顶点的入度都要减一(新图) indegree[v] -= 1 if indegree[ v] == 0: #更新入度表后,可能出现新的入度为0的点,此时要加入栈,具体是将indegree[v]=zerov,即新点v的元素指向下一个 indegree[ v] = zerov #入度为0的点,也就是老的zerov,然后再更新zerov=v,更新栈顶,继续重复上述过程 zerov = v #随着顶点不断从入读表中弹出,不断产生新的入度为0的点,进而不断弹出入度为0的元素,当弹出vnum个时,得到排序 return toposeq
def ee_time(graph, toposeq): vnum = graph.get_num() ee = [0] * vnum #初始化各事件最早发生事件为0 for i in toposeq: #按照拓扑序列的事件顺序遍历事件 for j, w in graph.out_edge(i): #以每个事件寻找其临接事件,然后更新临接事件的最早发生事件 if ee[j] < (ee[i] + w): #一个事件可能被多此找到,因此寻找最大的那个最早发生事件 ee[j] = ee[i] + w return ee
def le_time(graph, toposeq, ealast): vnum = graph.get_num() le = [ealast] * vnum for k in range(vnum - 2, -1, -1): #显然寻找最迟发生事件需要逆拓扑排序寻找,vnum-1最后一个的le是确定的 i = toposeq[k] for j, w in graph.out_edge(i): #找到事件i的临接事件,对其最迟发生时间更新 if (le[j] - w) < le[i]: #如果计算出的最迟发生时间le[i]-w,比原先的更小,那么更新它 le[i] = le[j] - w return le
def crt_path(graph, ee, le): #根据ee,le和graph来寻找关键活动序列 vnum = graph.get_num() crt_ac = [] for i in range(vnum): for j, w in graph.out_edge(i): #不能直接对比ee,le,因此这样只能得到关键活动,没办法得到关键路径 if ee[i] == (le[j] - w): #得到关键路径,遍历时间,对于时间i,它的临接时间j被遍历,如果满足 #ee[i]=le[j]-w le[i] crt_ac.append( ((i, j), (ee[i], le[i]))) #输出的是vi,vj时间之间的活动集合,同时加上(ee[i],le[i])检验一下 return crt_ac, ee, le
def all_shaort_paths(graph): vnum = graph.get_num() a = [[graph.get_edge(i, j) for j in range(vnum)] for i in range(vnum)] #复制原图的权重表 path = [[-1 if a[i][j] == inf else j for j in range(vnum)] for i in range(vnum)] #创建轨迹表 #其元素path[i][j]表示vi到vj路径上面的下一个点 for k in range(vnum): for i in range(vnum): for j in range(vnum): if a[i][j] > a[i][k] + a[k][j]: a[i][j] = a[i][k] + a[k][j] path[i][j] = path[i][k] #运用了性质vi到v的最短路径的v的前一个点v·, # 那么vi到v`也是最短路径,那么下一个顶点都是相同的 return (a, path)
def Dij_min_spanforest(graph, vo_): vnum = graph.get_num() #记录长度 paths = [None] * vnum #轨迹记录表,同时按是不是None来区分该顶点在不在U中 cands = Prique_heap([(0, vo_, vo_) ]) #记录V-U中顶点的最短路径的优先序列,只有U中顶点的临接点在其中,不在的长度为无穷 counter = 0 #计数器 while counter < vnum and not cands.is_empty(): lens, u, v = cands.dequeue() #弹出最短路径最短的一个临接顶点,即vmin=v if paths[v] is not None: #如该定点在U中不满足条件,跳过 continue paths[v] = (u, lens) #满足条件,记录该顶点,加入U,格式是上一顶点再加上路径长度 counter += 1 for j, w in graph.out_edge(v): #由于vmin的加入,vmin的临接顶点的最短路径可能会发生变化,进行更新。 if paths[j] is None: cands.enqueue((lens + w, v, j)) #更新加入vmin的不再U中的临接顶点及其最短路径 return paths #返回得到的路径表
def P_MST(graph): vnum = graph.get_num() mst = [None] * vnum #建立一个最小生成树的路径表,同时也充当表示最小生成树 #已包含的顶点的作用,比如第i个元素的值不是none代表最小生成树 #包含第i个顶点 cands = Prique_heap([(0, 0, 0)]) #初始时将初始点的父节点(它本身)表示的临接边加入优先序列 counter = 0 #计数值 while counter < vnum and not cands.is_empty(): w, vi, vj = cands.dequeue() #从优先序列中取出权值最小的临接边(一个点在U,一个点在V-U) if mst[vj] is not None: #这个临接边不满足条件vj在V-U中,也是就是一个点在生成树集之外 continue #跳过这条边 mst[vj] = ((vi, vj), w) #将满足条件的边加入到路径表,同时也表示第vj个顶点已经加入到生成树集 counter += 1 for j, w in graph.out_edge(vj): #将新加入的vj的满足条件的临接边(终点在V-U之外)加入到优先队列 #此时队列中包含V-U中的顶点的所有的还未弹出的临接边(继续找最小的) if mst[j] is None: cands.enqueue((w, vj, j)) return mst
def critical_paths(graph): def ee_time(graph, toposeq): vnum = graph.get_num() ee = [0] * vnum #初始化各事件最早发生事件为0 for i in toposeq: #按照拓扑序列的事件顺序遍历事件 for j, w in graph.out_edge(i): #以每个事件寻找其临接事件,然后更新临接事件的最早发生事件 if ee[j] < (ee[i] + w): #一个事件可能被多此找到,因此寻找最大的那个最早发生事件 ee[j] = ee[i] + w return ee def le_time(graph, toposeq, ealast): vnum = graph.get_num() le = [ealast] * vnum for k in range(vnum - 2, -1, -1): #显然寻找最迟发生事件需要逆拓扑排序寻找,vnum-1最后一个的le是确定的 i = toposeq[k] for j, w in graph.out_edge(i): #找到事件i的临接事件,对其最迟发生时间更新 if (le[j] - w) < le[i]: #如果计算出的最迟发生时间le[i]-w,比原先的更小,那么更新它 le[i] = le[j] - w return le #ee,le中的顺序都是代表拓扑排序的顶点(事件)的顺序 def crt_path(graph, ee, le): #根据ee,le和graph来寻找关键活动序列 vnum = graph.get_num() crt_ac = [] for i in range(vnum): for j, w in graph.out_edge(i): #不能直接对比ee,le,因此这样只能得到关键活动,没办法得到关键路径 if ee[i] == (le[j] - w): #得到关键路径,遍历时间,对于时间i,它的临接时间j被遍历,如果满足 #ee[i]=le[j]-w le[i] crt_ac.append( ((i, j), (ee[i], le[i]))) #输出的是vi,vj时间之间的活动集合,同时加上(ee[i],le[i])检验一下 return crt_ac, ee, le toposeq = toposort(graph) #print(toposeq) if not toposeq: #由于不存在入度为0的点,因此graph不存在拓扑序列 return False ee = ee_time(graph, toposeq) le = le_time(graph, toposeq, ee[graph.get_num() - 1]) return crt_path(graph, ee, le)
def K_MST(graph): vnum = graph.get_num() reps = [i for i in range(vnum)] #建立一个指示表,看表示不同的顶点是否在一个连通图 edges = [] for i in range(vnum): #构建包含所有边的表 for j, w in graph.out_edge(i): edges.append((w, i, j)) edges.sort() #将按从小大的顺序排列 mst = [] #记录最小生成图边的表,完成时应该包含n-1条边 #counter=0 #计数值,当计数到n-1时退出,证明已经得到最小生成树 for w, vi, vj, in edges: if reps[vi] != reps[vj]: #新加入的最小边有效的前提是端点不再一个连同图内 mst.append(((vi, vj), w)) #counter+=1 if len(mst) == vnum - 1: break rep, orep = reps[vi], reps[vj] #更新维护指示表,使新加入的边的端点在一个连通图内 for x in range(len(reps)): if reps[x] == orep: reps[x] = rep return mst