不管 prim 算法还是 kruskal 算法都基于 “当前可见最短边” 作贪心策略,但这并不适合分布式并行操作,比方说所有节点一起构建最小生成树,这些算法都显得同步开销过大,甚至导出错误的结果。
最近研究并构建最大流多路径传输协议的模型,也懒得翻书,在早上冒大雨跑步时思考,后来发现 STP(Spanning Tree Protocol) 大致上(并非完全一致)也是这思路。分布式环境下必须要并行操作,而这需要一个新路子。
先说基础,连通图中环路上的最大边一定不在最小生成树中。这很容易用反证法证明:
这也是个自由度问题,因为事情必须这样,不这样就会有不可能的结果。反之,“一个环的最小边一定在最小生成树中” 却不正确。
环最小边不一定在最小生成树,双环叠加时,同一边可以同时最小和最大正负对冲,不能同时保留,但最大边一定不在最小生成树,同时将它们都砍掉可正正叠加,越砍权重越小:
基于此,只要找到一个环,砍掉它的最大边,砍掉所有环最大边后,事情就成了。
和最短路径树用广度优先遍历策略不同,遍历连通图的过程中发现所有环路,可采用深度优先策略结合回溯,参考 tarjan 算法,大致是下面代码:
def dfs(node, parent, visited, graph, cycle):
visited[node] = "grey"
for neigh in graph[node]:
if visited[neighbor] == "white":
dfs(neigh, node, visited, graph, cycle)
elif visited[neigh] == "grey" and neigh != parent:
# 发现环路,从当前节点到 neighbor 的路径构成一个环
# 回溯过程中直接冒泡最大边并标记,代码写不好,算了
cycle.append(list(reverse_path(path[node]) + [node, neigh, weight]))
visited[node] = "black"
def find_cycles(graph):
visited = {node: "white" for node in graph}
cycles = []
for node in graph:
if visited[node] == "white":
dfs(node, None, visited, graph, cycles)
return cycles
深度优先遍历结束后,直接把所有标记的边砍掉即可,时间复杂度被边数量,回溯深度影响,控制在 O(EV)。有个细节点,如果一条边同时是两个环的最大边,则需要再砍一刀,否则就需要再遍历一次而不可止:
以上算法只是一个可行性说明,在更普遍场景,这个算法应由多节点并行完成,各节点相互交互转发 [节点,边,权重] 的 list 信息来判断出环路,由最大边所连接节点自行 block 该边。我说的 STP 大致是这个意思,说的就是这。
仍以老图为例,给出一个交互示例,也用几何表示,给出用肉眼识别环路的方法:
通过交互消息发现环路,自己阻塞端口,这就是 STP 的绝佳实例。而对于上图右边部分,给一个菌类生长形态的类比:
浙江温州皮鞋湿,下雨进水不会胖。