相关文章:
数据结构–图的概念
图搜索算法 - 深度优先搜索法(DFS)
图搜索算法 - 广度优先搜索法(BFS)
图搜索算法 - 拓扑排序
图搜索算法-最短路径算法-戴克斯特拉算法
图搜索算法-最短路径算法-贝尔曼-福特算法
图搜索算法-最小生成树问题-克鲁斯卡尔算法(kruskal)
图搜索算法-最小生成树问题-普里姆算法(prim)
网络流
首先认识网络流(network-flows),它是一种类比水流的解决问题方法,与线性规划密切相关。网络流的应用已遍及通讯、运输、电力、工程规划、任务分派、设备更新以及计算机辅助设计等众多领域。
哪一种图才叫网络流图?先看一个标准的网络流图是什么样子,如图所示。
(1)首先它是一个有向图。
(2)图中有唯一的一个源点(起点)S(入度为0的结点)。
(3)图中有唯一的一个汇聚点(终点)T(出度为0的结点)。
(4)图中每一条边的值都是非负数,其值为边可以通过的最大容量。
满足以上四个特征的图,便可以称为网络流图。
注意:入度是指有向图的某个结点作为终点的次数和。出度是指有向图的某个结点作为起点的次数和。
最大流问题
最大流问题(maximum flow problem)是网络流图里面一种常见问题,它是一种组合最优化问题,就是要讨论如何充分利用装置的能力,使得运输的流量最大,以取得最好的效果。结合图6-28,最大流问题就是求得从起点【S】到终点【T】的最大流量。
面对这个问题,自然想到了贪心算法,每一条边都尽量使用最大流量,那么结果会不会是最大流量呢?请看下面例子,如图所示。
首先要知道两个基本规定。
(1)每一条的流量【f(e)】不超过其容量【c(e)】。
(2)除了起点S和终点T,流入量一定等于流出量。
现在可以通过深度优先或者广度优先寻找一条从起点到终点的路径,如【S-1-2-T】路径,每一条边的容量分别为【3,5,3】,所以此路径的最大流量为3,如图所示。
从上图得知没有其他通道了,那么最大流量就是3吗?还有其他通道没有用到,是不是浪费了?现在引入两个概念为残余容量(residual capacity)和残差图(Residual Graphs),定义如下。
R(e) = c(e) - f(e)
R(e)来表示残余容量,给流过的路径建立反向边,此网络流图也可以称为残留网络,如图所示。
在网络流图中还有一个规律,如图中从【1】到【2】已经用了3个单位流量(f=3),那么从正方向上残余容量为2(r=c-f=5-3=2)。从反方向上看,残余容量为3,可以这样理解,有多少流量就有多少残余容量。有了残留网络,便发现了一条新路径【S-2-1-T】,此路径上每一条边的残余容量为【2,3,2】,因此最大流量为2。此时再计算每条边上的流量f,【S-1】、【S-2】、【1-T】、【2-T】都比较简单,便是他们的最大流量。【1-2】边上的流量是多少了,可以知道【1-2】方向上的正向流量是3,反方向上的流量为2,因此【1-2】边上流量3-2=1,如图所示。
此网络流图的最大流量为5,一共两条路径,这两条路径可以称为增广路径。如路径【S-2-1-T】中,【S-1】和【1-T】是正向边,【2-1】是反向边。从刚才计算得知,正向边是增加流量,因此增加量不能让其流量大于其容量,反向边的流量则是减少流量,所以流量不能少于0,不然方向就改变了。
从贪心算法中增加残差图概念便是福德-富克逊(Ford-Fulkerson)算法,其算法步骤如下。
- (1)初始化最大流量max_flow=0。
- (2)从图中找一条增广路径P,若存在到步骤(3),若不存在返回max_flow,结束程序。
- (3)在P上找路径所有边中最小流量值为路径流量path_flow。
- (4)max_flow=max_flow+path_flow。
- (5)在P上所有正向边残余容量减去path_flow,反向边增加path_flow。
- (6)回到步骤(2)。
用此算法手动计算本节例子,如上图所示,以此来熟悉算法的计算过程。首先初始化最大流量max_flow为0,找到第一条增广路径【S-1-3-T】,每条边的残余容量为【4,2,4】,那么此路径最大流量为2,因此max_flow=2,此时网络流图状态如图所示。
同理找到第二条增广路径【S-1-2-T】,每条边的残余容量为【2,3,5】,此路径最大流量为2,因此max_flow=2+2=4,此时网络流图如图所示。
继续寻找增广路径,这条增广路径【S-3-T】,每条边上的残余容量为【7,2】,那么此路径最大流量为2,因此max_flow=4+2=6。下一条增广路径【S-3-1-2-T】,每条边上的残余容量为【5,2,1,3】,那么此路径最大流量为1,因此max_flow=6+1=7,此时网络流图的状态如下图所示。
注意:反向路径计算残余流量与正向路径的区别。如增广路径【S-3-1-2-T】中,边【3-1】是反向路径,因此残余容量为此时最大流量2,而不是容量与最大流量差0。
现在用代码表现此算法,首先需要用邻近矩阵表示网络流图,所以【GraphFordFulkerson】类将会复用【GraphArray】类进行图的输入,BFS()函数是广度优先搜索,略有变化,返回路径是规定了起点和终点。FordFulkerson()函数则为算法主程序,通过计算每条增广路径的最大流量求得总的最大流量,程序如下。
class GraphFordFulkerson(GraphArray):
# GraphArray
def BFS(self, s, t, parent):
"""广度优先搜索"""
visited =[False]*(self.amount) # 初始化所有结点都没有访问过
queue=[] # 用队列结构来记录访问历史
queue.append(s) # 首先访问起点s
visited[s] = True
while len(queue) > 0: # 当队列为空,说明全部结点已经遍历完成
current_point = queue.pop(0) # 获取访问历史的队头为当前结点
for ind, val in enumerate(self.graph[current_point]):
# 逐一访问当前结点的所有关联点
if visited[ind] == False and val > 0:
queue.append(ind) # 如果没有访问添加到已访问队列中
visited[ind] = True # 标记结点为已访问
parent[ind] = current_point # 记录遍历的路径链接
return True if visited[t] else False # 如果有路径能到终点t则返回True
def FordFulkerson(self, source, sink):
# 福德-富克逊算法,计算从起点到终点的最大流问题
parent = [-1]*(self.amount) # 这个列表记录路径链接
max_flow = 0 # 初始化最大总流量为0
while self.BFS(source, sink, parent): # 通过BFS来寻找增广路径
path_flow = float("Inf") # 路径上的流量,初始化为无穷大
s = sink
while(s != source):
# 寻找此增广路径上的最小流量
path_flow = min(path_flow, self.graph[parent[s]][s])
s = parent[s]
max_flow += path_flow # 把此增广路径上的流量加到总流量上
# 更新此增广路径上的残余容量
v = sink
while(v != source):
u = parent[v]
self.graph[u][v] -= path_flow # 正向边减去路径流量
self.graph[v][u] += path_flow # 反向边加上路径流量
v = parent[v]
print("总的最大流量为:",max_flow)
return max_flow
以例子进行输入,验证程序结果。
g = GraphFordFulkerson(['s','1','2','3','t'])
g.graph = [[0, 4, 0, 7, 0],
[0, 0, 3, 2, 0],
[0, 0, 0, 0, 5],
[0, 0, 0, 0, 4],
[0, 0, 0, 0, 0]]
source = 0 # 起点下标
sink = 4 # 终点下标
g.FordFulkerson(source, sink)
#-------------------结果------------------------
总的最大流量为: 7
更多内容
想获取完整代码或更多相关图的算法内容,请查看我的书籍:《数据结构和算法基础Python语言实现》