最大流与最小割问题

来源:互联网 发布:java网上订餐系统视频 编辑:程序博客网 时间:2024/06/04 18:24
最大流问题
    对于一个流网络G  (V,E),其流量 f 的最大值称为最大流,最大流问题就是求一个流网络的最大流。
    增广路定理:当且仅当由当前的流f 压得的残留网络f G 中不存在增广路径时,流f 的流量f 达到最大。
   根据增广路定理,我们可以设计出最基本的求最大流的方法,一开始将流网络G  (V,E)的流 f 置为零流,即对于(u,v)E时, f (u,v)  0。然后构建残留网络,寻找增广路径增广,再修改残留网络,重复此过程,直到无法找到增广路径。

   此方法(之所以不是算法,是因为实现方法很多)称为Ford-Fulkerson 方法。

伪代码如下:
FORD-FULKERSON-METHORD (G, s, t)
1 initialize flow f to 0
2 while there exists an augmenting path p
3 do augment flow f along p
4 return f

Ford-Fulkerson 方法 (G,s,t)
1 将各边上流量 f 初始化为 0
2 while 存在一条增广路径 p
3     do 沿路径 p 增广流量 f
4 return f

假设有向网络 G 中边 (i,j) 的容量为 c(i,j) ,当前流量为 f(i,j) ,则此边的剩余流量即为 r(i,j) = c(i,j) - f(i,j) ,其反向边的剩余流量为 r(j,i) = f(i,j) 。有向网中所有剩余流量 r(i,j) > 0 的边构成残量网络 G,增广路径p即是残量网络中从源点 s 到终点 t 的路径。

沿路径 p 增广流量 f 的操作基本都是相同的,各算法的区别就在于寻找增广路径 p 的方法不同。例如可以寻找从 s 到 t 的最短路径,或者流量最大的路径。




最小割问题
最小割是指流网络中容量最小的割。
Ford-Fulkerson 定理(最小割最大流定理):在流网络中,最小割的容量等于最大流的流量。
根据这个定理,我们就可以通过求流网络的最大流来得到最小割。



几种经典最大流算法

最大流算法分为两大类:增广路 (Augmenting Path) 和预流推进重标号 (Push Relabel) 。也有算法同时借鉴了两者的长处,如 Improved SAP


EK算法(Edmonds-Karp)

在无权边的有向图中寻找最短路,最简单的方法就是广度优先搜索 (BFS),E-K 算法就直接来源于此。每次用一遍 BFS 寻找从源点 s 到终点 t 的最短路作为增广路径,然后增广流量 f 并修改残量网络,直到不存在新的增广路径。E-K 算法的时间复杂度为 O(VE2),由于 BFS 要搜索全部小于最短距离的分支路径之后才能找到终点,因此可以想象频繁的 BFS 效率是比较低的。实践中此算法使用的机会较少。


Dinic算法(构造分层网络寻找最短增广路)

首先定义分层网络 AN(f)。在残量网络中从源点 s 起始进行 BFS,这样每个顶点在 BFS 树中会得到一个距源点 s 的距离 d,如 d(s) = 0,直接从 s 出发可到达的点距离为 1,下一层距离为2 ... 。称所有具有相同距离的顶点位于同一层,在分层网络中,只保留满足条件 d(i) + 1 = d(j) 的边,这样在分层网络中的任意路径就成为到达此顶点的最短路径。

Dinic 算法每次用一遍 BFS 构建分层网络 AN(f),然后在 AN(f) 中一遍 DFS 找到所有到终点 t 的路径增广;之后重新构造 AN(f),若终点 t 不在 AN(f) 中则算法结束。DFS 部分算法可描述如下:

Dinic算法模板

#include <iostream>#include <string.h>#include <stdio.h>using namespace std;const int INF=2e9;const int mm=999;const int mn=999;int node,s,t,edge;int ver[mm],flow[mm],next[mm];int head[mn],work[mn],dis[mn],q[mn];void init(int _node,int _s,int _t){    node=_node, s=_s, t=_t;    for(int i=0;i<node;++i)        head[i]=-1;    edge=0;}void addedge(int u,int v,int c){    ver[edge]=v,flow[edge]=c,next[edge]=head[u],head[u]=edge++;    ver[edge]=u,flow[edge]=0,next[edge]=head[v],head[v]=edge++;}bool Dinic_bfs(){    int i,u,v,l,r=0;    for(i=0;i<node;++i)  dis[i]=-1;    dis[ q[r++]=s ] = 0;    for(l=0;l<r;l++)    {        for(i=head[ u=q[l] ]; ~i ;i=next[i])            if(flow[i] && dis[ v=ver[i] ]<0)             {                 dis[ q[r++]=v ]=dis[u]+1;                 if(v==t) return 1;             }    }    return 0;}int Dinic_dfs(int u,int exp){    if(u==t) return exp;    for(int &i=work[u],v,temp; ~i ;i=next[i])    {        if(flow[i] && dis[ v=ver[i] ]==dis[u]+1 && ( temp=Dinic_dfs(v,min(exp,flow[i])) )>0)        {            flow[i]-=temp;            flow[i^1]+=temp;            return temp;        }    }    return 0;}int Dinic_flow(){    int ans=0,res,i;    while(Dinic_bfs())    {        for(i=0;i<node;++i) work[i]=head[i];        while( res=Dinic_dfs(s,INF) )  ans+=res;    }    return ans;}int main(){    int n,m,u,v,c;    while(scanf("%d%d",&m,&n)!=EOF)    {        init(n+1,1,n);        while(m--)        {            scanf("%d%d%d",&u,&v,&c);            addedge(u,v,c);        }        printf("%d\n",Dinic_flow());    }    return 0;}


Improved SAP算法

通常的 SAP 类算法在寻找增广路时总要先进行 BFS,BFS 的最坏情况下复杂度为 O(E),这样使得普通 SAP 类算法最坏情况下时间复杂度达到了 O(VE2)。为了避免这种情况,Ahuja 和 Orlin 在1987年提出了Improved SAP 算法,它充分利用了距离标号的作用,每次发现顶点无出弧时不是像 Dinic 算法那样到最后进行 BFS,而是就地对顶点距离重标号,这样相当于在遍历的同时顺便构建了新的分层网络,每轮寻找之间不必再插入全图的 BFS 操作,极大提高了运行效率。国内一般把这个算法称为 SAP...显然这是不准确的,毕竟从字面意思上来看 E-K 和 Dinic 都属于 SAP,我还是习惯称为 ISAP 或改进的 SAP 算法。

 与 Dinic 算法不同,ISAP 中的距离标号是每个顶点到达终点 t 的距离。同样也不需显式构造分层网络,只要保存每个顶点的距离标号即可。程序开始时用一个反向 BFS 初始化所有顶点的距离标号,之后从源点开始,进行如下三种操作:(1)当前顶点 i 为终点时增广 (2) 当前顶点有满足 dist[i] = dist[j] + 1 的出弧时前进 (3) 当前顶点无满足条件的出弧时重标号并回退一步。整个循环当源点 s 的距离标号 dist[s] >= n 时结束。对 i 点的重标号操作可概括为 dist[i] = 1 + min{dist[j] : (i,j)属于残量网络Gf}。


0 0
原创粉丝点击