首先介绍算法中的一些基本概念:
容量:c(u,v)。表示边 <u,v>最大可以承载的流量。
流量:f(u,v)。表示边 <u,v>中已经有多少流量。
残量:r(u,v)。表示边 <u,v>还可以走多少的流量会达到饱和。r = c – f。
为了理解最大流算法,可以形象的将网络中的各个边联想成水管。那么c和f表示水管内的最大水流量以及水流量。r表示这个水管还能够增加多少单位的水流量。而对于一条路径,其能够增加的最大水流量受限于该路径经过的所有水管中最小的r。
有了这个具象之后,不难理解网络流的三大性质:
1. “水流量”f(u,v)<=”最大水流量”c(u,v)
2. 对于任意一个”水管”,f(u,v) = -f(u,v)。(反对称性)
3. 对于任意一个非源点和宿点的网络结点;流入的水流量一定等于流出的水流量。
要想求出从源点到宿点最大可以流过多少的流量,EK算法的思路很简单,从零流量开始不断地在网络中增加流量,直到没有任意一条从源到宿的路径可以再增加流量为止。而再这个过程中,增加的所有流量和就是可以网络可以承受的最大流。
鉴于以上的分析,总结出EK算法的过程如下:
1. 在网络中求出一条从源到宿的路径。计算该路径能通过的最大流量,记做r’。(求源到宿的路径可以使用BFS)
2. 在网络中将步骤1计算出来的路径流量增加r’,那么每个边的残量r就减少了r’。得到了一个残量网络。(这一步骤称为增广)
3. 在残量网络中重复步骤1,2。直到步骤1不能求出新的路径时,所有r‘的和即为最大流。
假设我们有初始网络如下图:
首先在步骤1中我们得到的路径1->2->3->4。该路径上可以流过的流量为2。得到残量网络如下,图中边上的二元组表示(f,c):
图中有一些奇怪的橙色边,在原始网络中是没有的。这些边称为反向弧。以1->2的反向弧为例,其r = c – f = 0 – (-2) = 2。
那么为什么要设立这些反向弧呢?这个残量网络中,如果只有正向的弧,我们不难发现,已经无法增广了。但是从原始网络中,从肉眼都可以看出最大流为3。如果不继续增广,结果显然不对。
但是在增加了反向弧之后,尚有一条增广路1->3->2->4,流量为1。
首先,在引入反向弧之后,它还是满足网络流的三大性质的,因此,是一个合法的网络流。其次,两个流1->2->3->4和1->3->2->4,相加之后可以理解为整个网络在2->3边上走了一个单位流量。
EK算法在得到一条增广路之后,求残量网络都是以路径能够承受的最大流量去算的。而这个最大的流量配出去之后不一定就是对的。那么反向弧的设立为之前配错的流量提供了一个可以反悔的机会。
EK算法编码如下:
#-*- coding: utf-8 -*-
topo = { 1 : {2 : 2, 3 : 1},
2 : {1 : 0, 3 : 2, 4 : 2},
3 : {1 : 0, 2 : 0, 4 : 2},
4 : {2 : 0, 3 : 0} }
INF = 0xFFFFFFFF
def ek_bfs(s, t):
flag = [0 for i in range(len(topo) + 1)]
return ek_bfs_core(flag, s, t, INF)
def ek_bfs_core(flag, s, t, r):
flag[s] = 1
if s == t: return r
for node, cap in topo[s].items():
if flag[node] == 0 and cap > 0:
new_r = ek_bfs_core(flag, node, t, min(r, cap))
if new_r != 0:
topo[s][node] -= new_r
topo[node][s] += new_r
return new_r
return 0
def ek(s, t):
total_f = 0
while True:
f = ek_bfs(s, t)
if f == 0: break
total_f = total_f + f
return total_f
if __name__ == '__main__':
print ek(1, 4)
执行可得,结果为3。