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()
