网络流是一个适用范围相当广的模型,相关的算法也非常多。尽管如此,网络流中的概念,思想和基本算法并不难理解。
最大流问题(Maximum-Flow Problem)
增广路算法
算法思想:从零流(所有边的流量均为0)开始不断增加流量,保持每次增加流量后都满足容量限制(f(u,v)<=c(u,v),c表示最大流量,f表示实际流量),斜对称性(f(u,v)=-f(v,u))和流量平衡(除了终起点,其他点的入与出相等)3个条件
我们的算法基于这样的一个现实:残量网络中任何一条从s到t的有向道路都对应一条原图中的增广路(augmenting path)——只要求出该道路中所有残量的最小值d,把对应的所有边上的流量增加d即可,这个过程称为增广(augmenting).不难验证,如果增广前的流量满足3个条件,增广后仍然满足。显然,只要找到残量网络中存在增广路,流量就可增大。可以证明它的逆命题也成立:如果残量网络中不存在增广路,则当前流就是最大值。这就是著名的增广路定理。
“找任意路径”最简单的办法无疑是用DFS,但很容易找出让他很慢的例子。一个稍微好一些的方法就是使用BFS,它足以应对数据不刁钻的网络流题目。这就是Edmonds-Karp算法。在下面的代码中,源点和汇点保存在变量s和t中,运行结束后,s-t的净流量保存在变量f中。
queue<int> q;
memset(flow,0,sizeof(flow));
f=0;
for(;;)/每次用for循环找一条最短的增广路径
{
memset(a, 0, sizeof(a));
a[s]=INF;
q.push(s);
while(!q.empty()) //BFS找增广路
{
int u=q.front();q.pop();
for(int v=1;v<=n;v++) if(!a[v]&& cap[u][v]>flow[u][v]) //找到新结点v
{
p[v]=u;q.push(v); //记录v的父亲,并加入FIFO队列
a[v]=a[u]<?cap[u][v]-flow[u][v]; //s-v路径上的最小残量
}
}
if(a[t]==0) break; //找不到,则当前流已经是最大流
for(int u=t;u!=s;u=p[u]) //从汇点往回走
{
flow[p[u]][u]+=a[t];//更新正向流量
flow[u][p[u]]-=a[t];//更新反向流量
}
f+=a[t];//更新从s流出的总流量
}
最小割最大流定理
最小费用最大流问题
queue<int> q;
int d[maxn];
memset(flow,0,sizeof(flow));
c=f=0;
for(;;)
{
//bellman-ford算法开始(在残量网络中找s-t最短路)
bool inq[maxn];
for(int i=0;i<n;i++) d[i]=(i==s?0:INF);
memset(inq,0 ,sizeof(inq));
q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();
inq[u]=false;
for(int v=0;v<n;v++) if(cap[u][v]>flow[u][v]&& d[v]>d[u]+cost[u][v])
{
d[v]=d[u]+cost[u][v];
p[v]=u;
if(!inq[v])
{
inq[v]=true;
q.push(v);
}
}
}
//bellman-ford算法到此结束
if(d[t]==INF) break; //汇点不可达,表明当前流已经是最小费用最大流
int a=INF;
for(int u=t;u!=s;u=p[u]) a<?=cap[p[u]][u]-flow[p[u]][u]; //计算可改进量
for(int u=t;u!=s;u=p[u]) //增广
{
flow[p[u]][u]+=a;
flow[u][p[u]]-=a;
}
c+=d[t]*a;//更新总费用和流量
f+=a;
}