上一篇中介绍了网络流的基础,最大流最小割定理的证明,下面来看如何求一个容量网络的最大流,这里介绍四种算法:EK算法、SAP算法、DINIC算法、HLPP算法。这四种算法中,前三种基于增广路,最后一种基于预流推进。
基于增广路的算法
Ford-Fulkerson算法
先来简单提一下Ford-Fulkerson算法。
在上一节中证明了,如果一个可行流中没有增广路,那么此时这个可行流的流量就是最大流,因此Ford-Fulkerson算法就是在一个可行流中不断地遍历寻找增广路,如果有增广路,那么就在这个增广路上做调整(前向弧流量增加,后向弧流量减少)来消除增广路,当在整个可行流中再也没法找到一条增广路时,就得到了最大流。这一思想非常简单,也很好理解,但问题的关键是:如何在一个可行流中高效地寻找增广路?Ford-Fulkerson用的是一种标号法,但是那种方式的时间复杂度可能会依赖于网络流中各边的容量,在最坏的情况下复杂度是O(Ef)(E为边数,f为所有边流量的最大值),例如下图中,如果在查找增广路时先选择A->B->C->D,再选择A->C->B->D,再走A->B->C->D……如此就需要走2000次,而实际上直接走A->B->D,A->C->D两次就完成了。而Furd-Fulkerson算法确实有可能会如前一种的方式进行,因此就不具体介绍了。
EK、SAP、DINIC算法都基于这样的消除增广路的思想,但它们给出了更好的查找增广路的方式,下面来分别来看这三种算法。
EK算法
最简单的算法莫过于暴力搜索,而EK算法正是如此。
在每次搜索增广路的时候,都采取BFS的策略,将所有的从源点到汇点的路径都找出来,那么如果有增广路,就一定可以将它找出来。因此采用BFS策略首先是正确的,来看一下它的代码实现:
//capacity:容量
//flow:流量
//parent:记录在一条增广路中每个节点的前一个节点
//alpha:记录在增广路中当每个节点所能调整的流量的最大值
int EK(int m)
{
//初始化操作
int result = 0;
for (int i = 1; i <= m; i++) parent[i] = alpha[i] = 0;
queue<int> vertexQueue;
while (true)
{
memset(alpha, 0, sizeof(alpha));
alpha[1] = INF;
vertexQueue.push(1);
//BFS过程
while (!vertexQueue.empty())
{
int vtop = vertexQueue.front();
vertexQueue.pop();
for (int i = 1 ; i <= m ; i ++ )
{
//如果目标节点还未在增广路中出现并且可以调整流量
if (!alpha[i] && flow[vtop][i] < capacity[vtop][i])
{
parent[i] = vtop;
alpha[i] = min(capacity[vtop][i] - flow[vtop][i], alpha[vtop]);
vertexQueue.push(i);
}
}
}
//汇点可调整流量为0,说明没有增广路了,算法结束
if (alpha[m] == 0)
{
return result;
}
//汇点可调整流量不为0,那么找到了增广路,增广路上所有节点做流量调整
for (int i = m; i != 1; i = parent[i])
{
flow[parent[i]][i] += alpha[m];//前向弧流量增加
flow[i][parent[i]] -= alpha[m];//后向弧流量减少
}
//由于一开始流量都为0,调整多少能量就代表整个可行流的流量增加了多少
result += alpha[m];
}
}