[学习笔记] MaxFlow最大流算法的简单笔记
MaxFlow最大流算法的简单笔记
概念
Forward Graph: 用于存储已经分配出去的流量。
Residual Graph: 用于存储当前时刻最大能够分配的流量和最大可以退回的流量。这个东西是个核心。
Residual Graph是我学习中最疑惑的一个东西,感觉老师并没有讲清楚,他到底是一个无向图(入边和出边权重始终相等),还是说入边和出边不等?可能是我之前搞混了,总是将其与Forward Graph混在一起理解。实际上,Residual Graph既存储着前向里能够分配的最大容量(与Forward Graph相加等于整个图的最大容量),也存储着反向能退回的最大容量。
在起始阶段,所有边的分配流量是0,那么Forward Graph是个全0的矩阵,Residual Graph就等于整个图的最大容量矩阵。所以之所以老没明白是因为书上都是从非0流量开始讲的。
每次完成一次增广路的更新,就需要更新Residual Graph了,此时正向还能最大配分的流量就等于原始最大能配分的流量减去已经配分的流量,反向的就等于已经配分的流量。
其他东西没啥可记的,到处都是教程,残差网络是唯一让我不解的东西。
算法流程
找增广路是用BFS记录每个元素上一个访问的元素来找路径的。(如果老师的ppt能像这张图一样简洁明了而不是一堆废话,那学习将会是一件轻松快乐的事情。)
Coding
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
import copy
class MAXFlowSolver():
def __init__(self,C):
self.C = C
self.forward_G = np.zeros(C.shape,dtype = np.int) # 已经分配出去的
self.residual_G = C.copy() # 可以向前分配的最大流量 和 可以返还的最大流量
def find_augment_path(self):
n = self.residual_G.shape[0]
'''
用BFS能轻易判断是否存在增广路,但是要得到增广路的路径有点烦。
我的思路是每次在BFS入队的时候把他的前一个访问的元素保存起来,
这样在path_reversed这个list里面,反向track就能得到从起点到
终点的增广路的路径了。
'''
path_reversed = -1 * np.ones((n,),dtype = np.int) # 记录反向追踪路径
visited = np.zeros((n,)) # 记录是否被访问过
que = [0] # BFS队列
flag = False # 用于标志有没有增广路
while len(que) > 0:
p = copy.copy(que[0])
visited[p] = 1
que.remove(p)
for i in range(n):
if self.residual_G[p,i] > 0 and not visited[i]: # 每次在残差网络中寻找增广路
que.append(i)
path_reversed[i] = p
if i == n-1: # 有到终点的路径
flag = True
break
path = [n-1]
p = path_reversed[-1]
bottleneck = np.inf
while not(p == -1):
path.append(p)
p = path_reversed[p]
path = path[::-1]
return_path = []
for i in range(len(path) - 1):# 计算bottleneck
bottleneck = min(bottleneck,self.residual_G[path[i],path[i+1]])
return_path.append((path[i],path[i+1]))
return flag,return_path,bottleneck
def is_a_forward_path(self,u,v):
return self.C[u,v] > 0
def __call__(self):
while True:
ret ,path, bottleneck = self.find_augment_path()
if not ret: # 没有增广路就停止
break
for u,v in path: # 有增广路就更新分配网络
if self.is_a_forward_path(u,v):
self.forward_G[u,v] += bottleneck
else:
self.forward_G[v,u] -= bottleneck
self.update_residual_G() # 每次更新完一条路之后更新残差网络
max_flow = np.sum(self.forward_G[0])
return self.forward_G, max_flow
def update_residual_G(self):
n = self.C.shape[0]
for i in range(n):
for j in range(n):
if self.C[i,j]:
self.residual_G[i,j] = self.C[i,j] - self.forward_G[i,j] # 最大容许配分的减去已经配分的
if self.forward_G[i,j]:
self.residual_G[j,i] = self.forward_G[i,j] # 最大容许退还的
if __name__ == "__main__":
C = np.array([
[0,27,27,0,0,0,0,0,0],
[0,0,0,5,12,10,0,0,0],
[0,0,0,0,15,0,0,12,0],
[0,0,0,0,0,8,6,0,0],
[0,0,0,3,0,0,6,10,0],
[0,0,0,0,0,0,0,0,18],
[0,0,0,0,0,0,0,0,12],
[0,0,0,0,0,0,0,0,22],
[0,0,0,0,0,0,0,0,0],
])
# C = np.array([
# [0,1,1,0],
# [0,0,1,1],
# [0,0,0,1],
# [0,0,0,0],
# ])
solver = MAXFlowSolver(C)
#print(solver.find_augment_path())
f,maxflow = solver()
print(f,maxflow)
nodes = ["A","B","C","D","E","F","G","H","I"]
G = nx.DiGraph()
for i in range(len(nodes)):
for j in range(len(nodes)):
if C[i,j] >0:
G.add_edges_from([(nodes[i],nodes[j])],weight = C[i,j])
edge_labels=dict([((u,v,),d['weight'])
for u,v,d in G.edges(data=True)])
pos=nx.spring_layout(G)
val_map = {'A': 1.0,
'I': 0.0}
values = [val_map.get(node, 0.45) for node in G.nodes()]
nx.draw_networkx_edge_labels(G,pos,edge_labels=edge_labels,edge_cmap=plt.cm.Reds)
nx.draw(G,pos = pos,node_color = values)
plt.show()