文章目录
一、图的应用-拓扑排序
拓扑排序Topological Sort
- 从工作流程图得到工作次序排列的算法,称为“拓扑排序”
- 拓扑排序处理一个有向无环图DAG,输出顶点的线性序列。使得两个顶点v,w,如果图中有(v,w)边,在线性
序列中v就出现在w之前。 - 拓扑排序广泛应用在依赖事件的排期上,还可以用在项目管理、数据库查询优化和矩阵乘法的次序优化上
拓扑排序的实现
- 拓扑排序可以采用深度优先搜索DFS实现。
- 将工作流程建立为图,工作项是节点,依赖关系是有向边。工作流程图一定是个有向无环图DAG,否则出现循环依赖。
- 对DAG图调用DFS算法,以得到每个顶点的“结束时间”,按照每个顶点的“结束时间”从大到小排序,输出这个次序下的顶点列表
- 根据遍历次序的不同,拓扑排序的结果可能有多个
示例:
# 将工作流程建立为图
dfs_g = DFSGraph()
dfs_g.add_edge("3/4 cup milk", "1 cup mix")
dfs_g.add_edge("1 egg", "1 cup mix")
dfs_g.add_edge("1 tbl oil", "1 cup mix")
dfs_g.add_edge("1 cup mix", "pour 1/4 cup")
dfs_g.add_edge("1 cup mix", "heat syrup")
dfs_g.add_edge("heat griddle", "pour 1/4 cup")
dfs_g.add_edge("pour 1/4 cup", "turn when bubbly")
dfs_g.add_edge("turn when bubbly", "eat")
dfs_g.add_edge("heat syrup", "eat")
dfs_g.dfs()
res = []
for i in dfs_g.vertexes.values():
res.append((i.finish, i.key))
# 按照每个顶点的“结束时间”从大到小排序
res.sort(key=lambda x: x[0], reverse=True)
print("拓扑排序结果:", res)
### 输出结果
拓扑排序结果: [(18, 'heat griddle'), (16, '1 tbl oil'), (14, '1 egg'), (12, '3/4 cup milk'), (11, '1 cup mix'), (10, 'heat syrup'), (8, 'pour 1/4 cup'), (7, 'turn when bubbly'), (6, 'eat')]
二、图的应用-强连通分支
强连通分支Strongly Connected Components
- 强连通分支,定义为图G的一个子集C。C中的任意两个顶点v,w之间都有路径来回,即(v,w)(w,v)都是C的路径,而且C是具有这样性质的最大子集
- Transposition转置:一个有向图G的转置GT,定义为将图G的所有边的顶点交换次序,如将(v,w)转换为(w,v)。图和转置图在强连通分支的数量和划分上,是相同的。
- 常用强连通分支算法有Tarjan算法、Gabow算法(对Tarjan的改进)和Kosaraju算法,这些算法的基本思想是进行深度优先搜索,并通过标记来识别强连通分支。
寻找强连通分支(在图中发现高度聚集节点群)的应用
- 网络分析:
- 在社交网络分析中,强连通分支可以帮助识别紧密联系的小团体或者社区。
- 在通信网络中,强连通分支可用于优化路由策略,提高网络的稳定性和效率。
- 算法设计:在算法设计中,特别是在有向图的处理算法中,例如拓扑排序,识别强连通分支可以简化问题,使得算法更加高效。
- 系统建模:在系统动态和反馈系统分析中,强连通分支能够揭示系统内部各部分之间的相互作用和依赖关系。
- 程序分析:在软件工程中,特别是在编译器的控制流分析中,强连通分支有助于理解程序中的循环结构,优化代码。
- 经济学和博弈论:在经济学模型中,强连通分支可以用来分析市场中的不同参与者之间的相互作用和影响。
- 生物信息学:在生物信息学中,可以通过分析蛋白质之间的相互作用网络,利用强连通分支来识别关键的生物分子和途径。
- 数据挖掘:在数据挖掘中,识别大型数据库中的强连通分支有助于发现数据之间的深层次联系。
强连通分支算法-Kosaraju算法思路
- 首先,对图G调用深度优先搜索DFS算法,为每个顶点计算“结束时间”;
- 然后,将图G进行转置,得到GT;
- 再对GT调用DFS算法,但在dfs函数中,对每个顶点的搜索循环里,要以顶点的“结束时间”倒序的顺序来搜索。
- 每次DFS访问到的节点构成一个强连通分支
示例
### 更新了DFSGraph类
class DFSGraph(Graph):
def __init__(self):
super().__init__()
self.time = 0
def dfs(self, keys_order=None):
"""keys_order参数:可指定遍历顶点的次序"""
# 初始化顶点颜色和前驱
for v in self:
v.set_color("white")
v.set_pred(-1)
# 遍历未访问的顶点
if keys_order:
for key in keys_order:
v = self.vertexes.get(key)
if v.get_color() == "white":
scc = [] # 记录每个强连通分支
self.dfsvisit(v, scc)
print(scc)
else:
for v in self:
if v.get_color() == "white":
self.dfsvisit(v)
def dfsvisit(self, start_v: Vertex, tmp=[]):
tmp.append(start_v.key)
start_v.set_color("gray")
self.time += 1
# 记录第几步访问到这个顶点
start_v.discovery = self.time
for next_v in start_v.get_connections():
# 递归访问未访问的顶点并记录前驱
if next_v.get_color() == "white":
next_v.set_pred(start_v)
self.dfsvisit(next_v, tmp)
start_v.set_color("black")
self.time += 1
# 记录在第几步完成了此顶点探索
start_v.finish = self.time
edges = [
["A", "B"],
["B", "C"],
["B", "E"],
["C", "C"],
["C", "F"],
["D", "B"],
["D", "G"],
["E", "D"],
["E", "A"],
["F", "H"],
["G", "E"],
["H", "I"],
["I", "F"],
]
# 对图G调用DFS算法,为每个顶点计算“结束时间”
dfs_g = DFSGraph()
for i in edges:
dfs_g.add_edge(i[0], i[1])
dfs_g.dfs()
res = []
for i in dfs_g.vertexes.values():
res.append((i.finish, i.key))
res.sort(key=lambda x: x[0], reverse=True) # “结束时间”从大到小排序
print("第一趟DFS按“结束时间”从大到小排序:")
print(res)
keys_order = [i[1] for i in res]
### 输出结果
第一趟DFS按“结束时间”从大到小排序:
[(18, 'A'), (17, 'B'), (16, 'E'), (15, 'D'), (14, 'G'), (10, 'C'), (9, 'F'), (8, 'H'), (7, 'I')]
第一趟DFS后
# 按照第一步记录的完成时间逆序对反转图进行DFS
dfs_gt = DFSGraph()
for i in edges:
dfs_gt.add_edge(i[1], i[0])
print("强连通分支如下:")
dfs_gt.dfs(keys_order)
### 输出结果
强连通分支如下:
['A', 'E', 'B', 'D', 'G']
['C']
['F', 'I', 'H']
转置后第二趟DFS后
强连通分支如下:
您正在阅读的是《数据结构与算法Python版》专栏!关注不迷路~
标签:排序,Python,self,dfs,算法,顶点,连通分支,数据结构 From: https://blog.csdn.net/zljgzw/article/details/144869432