大家好,小编最近新学了一个求解器OR-Tools,今天给大家介绍一下如何用OR-Tools求解器求解网络流问题中的最大流问题和 最小费用流问题。
前言
在进入正题之前,让我们简单讨论一下什么是最大流(Maximum Flows)问题和最小费用流(Minimum Cost Flows)问题。
在所有网络流量问题中,最关键的限制就是每条连接节点与节点的弧线都有容量限制。
而最大流问题就是如何充分利用网络运输的能力,使得运输的流量最大,以取得最好的效果,但须受容量限制。
关于最大流问题的更详细介绍参见:
最小费用流问题就是在给定网络模型中各节点的需求量和供应量的情况下,如何分配流量和路径,使得费用达到最小的问题。
OR-Tools求解器的调用
OR-Tools是谷歌开源的一个高效的运筹学工具包,包含整数线性规划,约束规划等问题的求解器,可以用于处理最困难的网络流、交通调度等组合优化和规划问题。官网链接:
https://developers.google.cn/optimization
想要用java调用相关求解器,小编推荐使用maven下载解决网络流问题所需的jar包。
Maven是一款帮助程序员构建项目的工具,我们只需要告诉Maven需要哪些Jar 包,它会帮助我们下载所有的Jar包,极大提升开发效率。
建立一个maven项目:
用以下两个文件替换生成的maven文件下的两个同名文件(欲下载相关文件,请留意评论区):
pom.xml文件在此处是用于告诉项目哪些jar包是需要下载的,而src文件中就是利用or-tools求解器解决网络流问题的代码。
将pom.xml替换后,如下图点击更新项目键就可以自动下载所需的jar包了。
代码简介
学会了如何调用,我就可以进入正题啦~
本文使用的的两个样例都是OR-Tools求解器官网推荐的样例,由于这样的案例最优解已知,更容易判断调用是否成功。
No.
01最大流问题
OR-Tools求解器解决最大流问题使用的是 push-relabel 算法。它最大的特点是一个结点一个结点地进行查看,每一步只检查当前结点的邻接点。(下文介绍的是push-relabel算法的通用思路,可能与OR-Tools求解器的求解思路有所不同)
push-relabel 算法的重要步骤是预流。预流过程的目的是找到溢出点。
回顾流的性质,满足容量限制以及流量守恒,预流和流最大区别在于,它弱化了流量守恒,满足以下不等式(s为源点,V表示所有节点的集合,u、v为任意节点,f(u,v)表示从节点v到节点u的流量):
直观意义是,对于结点u而言,允许其流出量小于流入量,似乎有部分流量被结点u储存起来。
定义这部分流量为超额流excess-flow:
对于除源点以外的结点u而言,如果有 e ( u ) >0,那么称结点u发生溢出。
对于给定流网络 G = ( V, E ),其源点为s,汇点为 t,f是G的预流,V表示所有节点的集合,E表示所有边的集合,如果函数 h : V → N满足
那么称函数h 是高度函数。
根据定义我们可知,如果对于结点对( u ,v ),有h( u )>h( v )+1成立,那么
1.3 推送操作(push)
如果结点u是一个溢出结点,残存容量Cf( u, v ) > 0,并且h( u ) = h( v ) + 1,则PUSH( u, v )适用于结点u和v。
伪代码如下所示:
u.e——保存结点u的超额流 u.h——保存结点u的高度 ∆f( u, v ) ——存放可以从结点u推送到结点v的流
如果推送操作后,残存网络中的边(u, v)达到饱和状态(即操作之后由Cf( u, v ) = 0),则称该推送操作为饱和推送;否则,成为非饱和推送。
如果结点u溢出,并且对于所有的边(u, v) ∈Ef,有u.h ≤ v.h,则基本操作RELABEL( u )适用于结点u。
直观上来说,结点u发生了溢出,但环顾四周,虽然有残存容量可用,但都是和它一样高的结点甚至比它高,超额流无法进行推送,所以我们对其进行Relabel。
伪代码如下所示:
上述伪代码的作用直观来说如是:环顾四周,找到比节点 u 高的结点中高度最小的,记为v,将u的高度赋值为 h ( v ) + 1。
下图清晰得说明了push-relabel 算法的大致过程:
push-relabel 算法的第一步永远是初始化预流(initialize-preflow),具体初始化过程如下图所示:
在初始化预流结束后,我可以进行预流(preflow)过程从而找到溢出点。
我们可以发现只要出现溢出结点,那就可以通过Push和Relabel这两种操作中的一种解决该溢出点,从而得到更优解。因此我们可以按照某种次序(无需特定)执行Push与Relabel操作。
将以上操作重复一定次数,就可以得到较好解了。
在初始化函数中,我们将连接源点 s 的每条边容量都发挥到最大,显然这是最大流的上界,之后的过程有种水往低处流的直观感受。如果某个结点存在超额流,即该结点溢出,它会尽力将超额流向地处推送,如果邻域内的结点都高于它或与之同高度,则抬高该点,使其超额流能够完成推送。
源点向整个网络推送了不低于最大流量的水流,而后网络中每个结点对自己收到的流量进行调节,最终达到一个平衡状态,网络中现存的水流即为最大流量,超额流全部通过抬高结点高度反推回源点。
System.out.println("Max Flow Problem - Simple interface");
final int[] tails = {0, 0, 0, 0, 1, 2, 3, 3, 4};
final int[] heads = {1, 2, 3, 4, 3, 4, 4, 5, 5};
final int[] capacities = {5, 8, 5, 3, 4, 5, 6, 6, 4};
final int expectedTotalFlow = 10;
MaxFlow maxFlow = new MaxFlow();
for (int i = 0; i < tails.length; ++i) {
maxFlow.addArcWithCapacity(tails[i], heads[i], capacities[i]);
}
声明解算器并添加弧线
addArcWithCapacity()方法添加具有从结束节点到开始节点的给定容量的弧线
if (maxFlow.solve(0, 5) == MaxFlow.Status.OPTIMAL) {
System.out.println("Total flow " + maxFlow.getOptimalFlow() + "/" + expectedTotalFlow);
for (int i = 0; i < maxFlow.getNumArcs(); ++i) {
System.out.println("From source " + maxFlow.getTail(i) + " to target " + maxFlow.getHead(i)
+ ": " + maxFlow.getFlow(i) + " / " + maxFlow.getCapacity(i));
}
} else {
System.out.println("There was an issue with the input.");
}
调用解算器并显示结果:solve()方法调用求解器并求出最优解,若最优解与样例所给出的最优解一致,则输出相应最优解,反之则输出错误提示。
No.
02最小费用流问题
OR-Tools求解器解决最大流问题使用的是cost-scaling push-relabel算法。该算法与push-relabel 算法类似,较为复杂,不适合展开讲。
详情请见:
https://developers.google.cn/optimization/reference/graph/min_cost_flow
System.out.println("Min Cost Flow Problem - Simple interface");
final int numSources = 4;
final int numTargets = 4;
final int[][] costs = {{90, 75, 75, 80}, {35, 85, 55, 65}, {125, 95, 90, 105}, {45, 110, 95, 115}};
final int expectedCost = 275;
定义数据:在样例中,共有8个节点,开始节点0、1、2、3,结束节点4、5、6、7,二维列表costs表示节点们的距离矩阵。
此处expectedCost为已知的最优解流量,用于测试调用是否成功。
MinCostFlow minCostFlow = new MinCostFlow();
for (int source = 0; source < numSources; ++source) {
for (int target = 0; target < numTargets; ++target) {
minCostFlow.addArcWithCapacityAndUnitCost(
source, numSources + target, 1, costs[source][target]);
}
}
for (int node = 0; node < numSources; ++node) {
minCostFlow.setNodeSupply(node, 1);
minCostFlow.setNodeSupply(numSources + node, -1);
addArcWithCapacityAndUnitCost()方法与最大流问题中的addArcWithCapacity()方法类似,作用都是从开始节点到结束节点添加一条弧线,不同的是addArcWithCapacityAndUnitCost()方法所画的弧线中还有费用属性。
if (minCostFlow.solve() == MinCostFlow.Status.OPTIMAL) {
final long totalFlowCost = minCostFlow.getOptimalCost();
System.out.println("total flow = " + totalFlowCost + "/" + expectedCost);
for (int i = 0; i < minCostFlow.getNumArcs(); ++i) {
if (minCostFlow.getFlow(i) > 0) {
System.out.println("From source " + minCostFlow.getTail(i) + " to target "
+ minCostFlow.getHead(i) + ": cost " + minCostFlow.getUnitCost(i));
}
}
} else {
System.out.println("No solution found");
}
调用解算器并显示结果:此处Status.OPTIMAL也表示此案例有已知的最优解,用于检测调用是否成功。接下来的代码就是用于输出结果。
输出结果如下:
除了网络流问题,OR-Tools求解器还可以解决如整数线性规划问题,约束规划问题等,感兴趣的小伙伴们可以尝试一下哟~
OR_Tools地址:https://developers.google.cn/optimization/flow
欲下载相关代码,请移步留言区
- END -
文案&排版:王思涵(华中科技大学管理学院本科一年级)
指导老师:秦虎老师(华中科技大学管理学院)
审稿:周航(华中科技大学管理学院本科二年级)
如对文中内容有疑问,欢迎交流。PS:部分资料来自网络。
如有需求,可以联系:
秦虎老师(华中科技大学管理学院,tigerqin1980@qq.com)
王思涵(华中科技大学管理学院本科一年级:2278159431@qq.com)
周航(华中科技大学管理学院本科二年级:zh20010728@126.com)