Wang Haihua
🚅 🚋😜 🚑 🚔
流在生活中十分常见,例如交通系统中的人流、车流、物流,供水管网中的水流,金融系统中的现金流,网络中的信息流。网络流优化问题是基本的网络优化问题,应用非常广泛。 网络流优化问题最重要的指标是边的成本和容量限制,既要考虑成本最低,又要满足容量限制,由此产生了网络最大流问题、最小费用流问题、最小费用最大流问题。 本文基于 NetworkX 工具包,通过例程详细介绍网络最大流问题、最小费用流问题、最小费用最大流问题的建模和编程。
网络流优化问题是基本的网络优化问题,应用非常广泛,遍及通讯、运输、电力、工程规划、任务分派、设备更新以及计算机辅助设计等领域。
流从源点流出、通过路径输送、流入到汇点,从而将目标从源点输送到汇点。流在生活中十分常见,例如交通系统中的人流、车流、物流,供水管网中的水流,金融系统中的现金流,网络中的信息流。
现实中的任何路径都有最大流量(容量)的限制,在网络中也是如此,并以边的容量(Capacity)表示,一条边的流量不能超过它的容量。
把这些现实问题抽象为网络流问题,其特征是:(1)有向图上的每条边具有容量限制;(2)从源点流出的流量,等于汇点流入的流量;(3)源点和汇点之外的所有中间节点,流出的流量等于流入的流量。
注意在网络流问题中有几组概念容易混淆:
源点/汇点,起点/终点,供应点/需求点:源点是只进不出的点,汇点是只出不进的点。源点/汇点 可以指定为问题的 起点/终点,但本质上源点/汇点是由网络结构特征决定的,而不是被指定的。供应点的供应量和需求点的需求量是固定/确定的,而源点/汇点的目标是发出/接收的流量最大,不是固定值。 容量 与 流量:容量是路径(边、弧)允许的最大流通能力,用 c(i,j) 表示;流量则是路径(边、弧)上的实际流量,用 f(i,j) 表示。
网络流优化问题最重要的指标是每条边的成本和容量限制,既要考虑成本最低(最短路径问题),又要满足容量限制(最大流问题),由此产生了网络最大流问题、最小费用流问题、最小费用最大流问题。
最大流问题(Maximun flow problem):已知每条边的容量,研究如何充分利用网络能力,使从源点到汇点的总流量最大,也即在容量网络中求流量最大的可行流。
最小费用流问题(Minimum cost problem):已知每条边的容量和单位流量的费用,对于给定的源点、汇点流量,研究如何分配流量和路径,使总费用最小,也即在容量费用网络中求成本最低的可行流。
最小费用最大流问题(Minimum cost maximum flow),已知每条边的容量和单位流量的费用,研究网络的流量最大的路径中,费用最小的路径。简单地说,就是满足最大流的路径可能有多条,需要从其中找到成本最低的路径。
Network 工具包求解网络流优化,包括最大流算法、最小割算法、最小费用流算法、最小费用最大流算法、容量缩放最小成本流算法。
在实际问题中,我们总是希望在完成运输任务的同时,寻求运输费用最低的方案。最小费用流问题,就是要以最小费用从出发点(供应点)将一定的流量输送到接收点(需求点)。在最小流问题中,供应点、需求点的数量可以是一个或多个,但每个供应点的供应量和需求点的需求量是固定的。
最小费用流问题可以用如下的线性规划问题描述
求解最小费用流问题的方法很多,常见的如:连续最短路算法(Successive shortest path)、消圈算法(Cycle canceling)、原始对偶算法(Primal dual)、网络单纯性算法(Network simplex)和非均衡网络流算法(Out of Kilter法)等。
网络单纯形是单纯形算法的一个特殊应用,它使用生成树基来更有效地解决具有纯网络形式的线性规划问题。网络单纯性为最小费用流问题提供了标准的解决方法,可以解决数万个节点的大型问题。
最小费用流问题最重要的应用是配送网络的优化,如确定如何从出发地运送到中转站再转运到客户的配送方案。运输问题、指派问题、转运问题、最大流问题、最短路径问题,都是特殊情况下的最小费用流问题。例如,最短路径问题是流量 v=1 的最小费用流问题,最大流问题是最大流量下的最小费用流问题。只要选定合适的权重、容量、流量,解决最小费用流的方法就能用来解决上述问题。
min_cost_flow()、min_cost_flow_cost() 是求解费用最小流问题的函数,通过调用网络单纯性算法函数 network_simplex() 求解。
min_cost_flow(G, demand='demand', capacity='capacity', weight='weight') min_cost_flow_cost(G, demand='demand', capacity='capacity', weight='weight')
主要参数:
G(NetworkX graph):有向图,边必须带有容量属性 capacity、单位成本属性 'weight' 。 demand (string):顶点的需求量属性 demand,表示节点的净流量:负数表示供应点的净流出量,正数表示需求点的净流入量,0 表示中转节点。缺省值为 0。 capacity (string):边的容量,缺省视为无限容量。 weight (string):边的单位流量的费用,缺省值为 0。 返回值:
flowDict (dict):字典类型,最小费用流的流经路径及各路径的分配流量 flowCost(integer, float):满足需求的最小费用流的总费用 NetworkXUnfeasible:输入的净流量(demand)不平衡,或没有满足需求流量的可行流时,抛出异常信息。 注意:费用最小流函数 min_cost_flow() 中并没有设定供应点、需求点,而是通过设置顶点属性 'demand' 确定供应点、需求点及各顶点的净流量,因而允许网络中存在多个供应点、需求点。
问题描述:
从 s 将货物运送到 t。已知与 s、t 相连各道路的最大运输能力、单位运量的费用如图所示(参见 3.6 程序运行结果图),图中边上的参数 (9,4) 表示道路的容量为 9,单位流量的费用为 4。求流量 v 的最小费用流。
问题分析:
这是一个最小费用流问题。用 NetworkX 的 nx.min_cost_flow() 函数或 nx.network_simplex() 函数即可求出从供应点到需求点的给定流量 v 的最小费用流。
程序说明:
图的输入。本例为稀疏的有向图,使用 nx.DiGraph() 定义一个有向图,用G.add_weighted_edges_from() 函数以列表向图中添加多条赋权边,每个赋权边以元组 (node1,node2,{'capacity':c1, 'weight':w1}) 定义属性 'capacity' 和 'weight'。注意必须以关键字 'capacity' 表示容量,以 'weight' 表示单位流量的费用。
nx.shortest_path() 用于计算最短路径,该段不是必须的。将最短路径的计算结果与最小费用流的结果进行比较,可以看到流量 v=1 时最小费用流的结果与最短路径结果是相同的。
nx.cost_of_flow()
用于计算最小费用最大流,该段不是必须的。将最小费用最大流的计算结果与最小费用流的结果进行比较,可以看到在最大流量 v=14 时最小费用流的结果与最小费用最大流结果是相同的。
最小费用流是基于确定的流量 v 而言的。流量 v 可以在程序中赋值;例程中 v 从 1 逐渐递增,计算所有流量下的最小费用流,直到达到网络容量的极限(如果再增大流量将会超出网络最大容量,没有可行流,计算最小费用流失败)。 NetworkX 计算最小费用流时不是在函数中指定源点、汇点和流量,而是通过向源点、汇点添加属性 demand 实现的。demand 为正值时表示净输入流量,demand 为负值时表示净输出流量,这使我们可以指定多源多汇。
nx.min_cost_flow()
返回最小费用流的路径和流量分配,字典格式;nx.min_cost_flow_cost() 返回最小费用流的费用值。nx.network_simplex() 也可以求最小费用流,返回最小费用流的费用值,路径和流量分配。
在最小费用流图中(最大流量 v=14),以边的标签显示了边的容量 c、单位流量的费用 w 和流量 f,如 (8,4),f=7 表示边的容量为 8,单位流量的费用为 4,分配流量为 7。
在最小费用流图中(最大流量 v=14),以不同颜色(edge_color='m')和宽度(width=2)表示最小费用流的边,未使用的流量为 0 (f=0)的边以黑色绘制。
import numpy as np
import matplotlib.pyplot as plt # 导入 Matplotlib 工具包
import networkx as nx # 导入 NetworkX 工具包
# 2. 最小费用流问题(Minimum Cost Flow,MCF)
# 创建有向图
G2 = nx.DiGraph() # 创建一个有向图 DiGraph
G2.add_edges_from([('s','v1',{'capacity': 7, 'weight': 4}),
('s','v2',{'capacity': 8, 'weight': 4}),
('v1','v3',{'capacity': 9, 'weight': 1}),
('v2','v1',{'capacity': 5, 'weight': 5}),
('v2','v4',{'capacity': 9, 'weight': 4}),
('v3','v4',{'capacity': 6, 'weight': 2}),
('v3','t',{'capacity': 10, 'weight': 6}),
('v4','v1',{'capacity': 2, 'weight': 1}),
('v4','t',{'capacity': 5, 'weight': 2})]) # 添加边的属性 'capacity', 'weight'
# 整理边的标签,用于绘图显示
edgeLabel1 = nx.get_edge_attributes(G2, 'capacity')
edgeLabel2 = nx.get_edge_attributes(G2, 'weight')
edgeLabel = {}
for i in edgeLabel1.keys():
edgeLabel[i] = f'({edgeLabel1[i]:},{edgeLabel2[i]:})' # 边的(容量,成本)
# 计算最短路径---非必要,用于与最小费用流的结果进行比较
lenShortestPath = nx.shortest_path_length(G2, 's', 't', weight="weight")
shortestPath = nx.shortest_path(G2, 's', 't', weight="weight")
print("\n最短路径: ", shortestPath) # 输出最短路径
print("最短路径长度: ", lenShortestPath) # 输出最短路径长度
# 计算最小费用最大流---非必要,用于与最小费用流的结果进行比较
minCostFlow = nx.max_flow_min_cost(G2, 's', 't') # 求最小费用最大流
minCost = nx.cost_of_flow(G2, minCostFlow) # 求最小费用的值
maxFlow = sum(minCostFlow['s'][j] for j in minCostFlow['s'].keys()) # 求最大流量的值
print("\n最大流量: {}".format(maxFlow)) # 输出最小费用的值
print("最大流量的最小费用: {}\n".format(minCost)) # 输出最小费用的值
# v = input("Input flow (v>=0):")
v = 0
while True:
v += 1 # 最小费用流的流量
G2.add_node("s", demand=-v) # nx.min_cost_flow() 的设置要求
G2.add_node("t", demand=v) # 设置源点/汇点的流量
try: # Youcans@XUPT
# 求最小费用流(demand=v)
minFlowCost = nx.min_cost_flow_cost(G2) # 求最小费用流的费用
minFlowDict = nx.min_cost_flow(G2) # 求最小费用流
# minFlowCost, minFlowDict = nx.network_simplex(G2) # 求最小费用流--与上行等效
print("流量: {:2d}\t最小费用:{}".format(v, minFlowCost)) # 输出最小费用的值(demand=v)
# print("最小费用流的路径及流量: ", minFlowDict) # 输出最大流的途径和各路径上的流量
except Exception as e:
print("流量: {:2d}\t超出网络最大容量,没有可行流。".format(v))
print("\n流量 v={:2d}:计算最小费用流失败({})。".format(v, str(e)))
break # 结束 while True 循环
edgeLists = []
for i in minFlowDict.keys():
for j in minFlowDict[i].keys():
edgeLabel[(i, j)] += ',f=' + str(minFlowDict[i][j]) # 取出每条边流量信息存入边显示值
if minFlowDict[i][j] > 0:
edgeLists.append((i, j))
maxFlow = sum(minFlowDict['s'][j] for j in minFlowDict['s'].keys()) # 求最大流量的值
print("\n最大流量: {:2d},\t最小费用:{}".format(maxFlow, minFlowCost)) # 输出最小费用的值
print("最小费用流的路径及流量: ", minFlowDict) # 输出最小费用流的途径和各路径上的流量
print("最小费用流的路径:", edgeLists) # 输出最小费用流的途径
# 绘制有向网络图
pos={'s':(0,5),'v1':(4,2),'v2':(4,8),'v3':(10,2),'v4':(10,8),'t':(14,5)} # 指定顶点绘图位置
fig, ax = plt.subplots(figsize=(8,6))
ax.text(6,2.5,"youcans-xupt",color='gainsboro')
ax.set_title("Minimum Cost Maximum Flow with NetworkX")
nx.draw(G2,pos,with_labels=True,node_color='c',node_size=300,font_size=10) # 绘制有向图,显示顶点标签
nx.draw_networkx_edge_labels(G2,pos,edgeLabel,font_size=10) # 显示边的标签:'capacity','weight' + minCostFlow
nx.draw_networkx_edges(G2,pos,edgelist=edgeLists,edge_color='m',width=2) # 设置指定边的颜色、宽度
plt.axis('on')
plt.show()
最短路径: ['s', 'v1', 'v3', 'v4', 't'] 最短路径长度: 9 最大流量: 14 最大流量的最小费用: 159 流量: 1 最小费用:9 流量: 2 最小费用:18 流量: 3 最小费用:27 流量: 4 最小费用:36 流量: 5 最小费用:45 流量: 6 最小费用:56 流量: 7 最小费用:67 流量: 8 最小费用:79 流量: 9 最小费用:91 流量: 10 最小费用:103 流量: 11 最小费用:115 流量: 12 最小费用:127 流量: 13 最小费用:143 流量: 14 最小费用:159 流量: 15 超出网络最大容量,没有可行流。 流量 v=15:计算最小费用流失败(no flow satisfies all node demands)。 最大流量: 14, 最小费用:159 最小费用流的路径及流量: {'s': {'v1': 7, 'v2': 7}, 'v1': {'v3': 9}, 'v2': {'v1': 2, 'v4': 5}, 'v3': {'v4': 0, 't': 9}, 'v4': {'v1': 0, 't': 5}, 't': {}} 最小费用流的路径: [('s', 'v1'), ('s', 'v2'), ('v1', 'v3'), ('v2', 'v1'), ('v2', 'v4'), ('v3', 't'), ('v4', 't')]
参考文献