上一篇中介绍了网络流的基础,最大流最小割定理的证明,下面来看如何求一个容量网络的最大流,这里介绍四种算法: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];
}
}
那么如何评估它的性能呢?首先可以确定的是,它不会出现像Ford-Fulkerson那样的问题,考虑上面那张图所示的情形,如果采用EK算法,是肯定不会走A->B->C->D的,为什么?因为采用BFS获取的路径一定是最短距离的路径,很明显上图中从源点到汇点的最短距离为3,因此EK算法能够避免Ford-Fulkerson遇到的问题。
但是仅仅如此还是不能对EK算法的性能有一个清晰的认识,需要知道其准确的时间复杂度。不妨先来看一个EK算法的运行实例:(图来自wiki)
红色的路径就是每次BFS所找到的增广路。从这张图中可以观察到一个事实:在每次BFS查找增广路之后,最短增广路的长度一定是非减的,也即对于每一个节点,它到源点的最短距离是非减的。这个性质可以有严格的证明,见《算法导论》(引理26.7 P426),直观上想象一下,如果对于一个给定的图确定了从源点到某一点的最短距离为 d d d,现在在这个图中去掉一些边,那么从源点到这一点要么变得不连通,要么距离会不变,要么距离会增大,绝对不可能减少。因为如果它减少为 d ′ < d d'<d d′<d,那么把去掉的这些边加回来,得到原图,这一过程并没有影响到 d ′ d' d′的这条路径,因此原图中还肯定存在距离为 d ′ d' d′的路径,这与 d d d为最短距离矛盾。增广路调整的过程,就相当于在原图中去掉了一些边,因为某些前向弧变成了满流,后向弧变成了零流,没办法再经过这些边了。
基于这一点,可以证明一个引理:
EK算法中所能找到的增广路的数量为O(VE)
证明:
每次调整增广路的时候,所调整的流量为所有边可调整流量的最小值,那么就定义具有最小值的那条边为 关键边,显然每条增广路都必须至少有一条关键边
设流f的源点为s,汇点为t,假设边(u,v)成为某次BFS搜索得到的增广路中的关键边,此时 d i s t ( s , v ) = d i s t ( s , u ) + 1 dist(s,v) = dist(s,u) + 1 dist(s,v)=dist(s,u)+1,在这次增广路流量调整后,这条边的可调整流量将变为0,也就是说(u,v)会从残存网络中消失。如果边(u,v)想要再度成为关键边,那么(u,v)的流量必须要减少,也就是说当(u,v)再度成为关键边时一定有 d i s t ′ ( s , u ) = d i s t ′ ( s , v ) + 1 dist'(s,u) = dist'(s,v)+1 dist′(s,u)=dist