利用Ford-Fulkerson算法求解网络流问题
code on github 这个算法我是从 《[数据结构与算法分析_Java语言描述(第2版)].韦斯》这本书里面学习的。
与其说是一个算法,不如说是一个框架。这个框架的思想很简单:
- 维护一个残余图(residual graph).
- 在残余图上寻找增广路径(augmenting path).
- 将增广路径信息更新到残余图上,继续步骤2直到找不出任何一条路径。
如何将增广路径更新到残余图上呢?假设选择了一条边(x, y, v), 那么
- graph[x][y] -= v. 在x->y这条边上减去v
- graph[y][x] += v. 同时增加一条反向边
这个框架的灵活之处就在于如何寻找增广路径,这些方法都是启发式的算法。启发效果好可以显著减少运行时间。 一种办法是使用类似Dijkstra的方法,每次从当前已知有最大流量的点进行扩展。书里面说可以证明,使用这种算法运行O(E * log(cap-max))次增广路径算法可以找到最大流,其中cap-max是边的最大容量。 如果每次不是寻找最大流量的点进行扩展,而只是使用普通的DFS找一条可行路径的话,那么最坏情况需要使用O(E*cap-max)次才能找到最大流。
如果增广算法的时间复杂度是O(E * lgV)的话,那么这个算法的时间复杂度就是O(E^2 * lgV * log(cap-max)). 不过我的代码里面时间复杂度是O(VE)而不是O(ElgV), 所以最终时间复杂度是O(VE^2 * log(cap-max)).
def find_augmenting_path(G: Graph, s, t):
n = len(G)
inf = 1 << 30
flow = [0] * n
flow[s] = inf
visit = [0] * n
parent = [-1] * n # 如果选取这个节点的话,那么从哪个点流向这个点
mat = G.mat
# 类似Dijkstra算法,不过选择最大的一个flow做扩展
while True:
max_flow = 0
max_node = None
for x in range(n):
# 选择一个点做扩展
if flow[x] > max_flow and not visit[x]:
max_flow = flow[x]
max_node = x
if max_node is None:
break
x = max_node
visit[x] = 1
for y, edge_value in enumerate(mat[x]):
if not visit[y]:
# 更新到达这个点的最大流
value = min(max_flow, edge_value)
if value > flow[y]:
flow[y] = value
parent[y] = x
# 回溯求出增广通路
edges = []
if flow[t] != 0:
x = t
while x != s:
edges.append((parent[x], x, flow[x]))
x = parent[x]
edges = edges[::-1]
# 返回所选择的边和这条边上的最大流
return edges, flow[t]
def update_redisual_graph(G, edges):
mat = G.mat
# 更新正向边权重,并且增加一条反向边
for (x, y, v) in edges:
mat[x][y] -= v
mat[y][x] += v
return G
def find_network_flow(G, s, t):
res = 0
while True:
edges, flow = find_augmenting_path(G, s, t)
if flow == 0:
break
res += flow
print(G.readable_edges(edges), flow)
update_redisual_graph(G, edges)
return res