网络流算法详解

网络流算法在许多实际问题中有应用,如匹配问题,著名的Hall婚姻定理。这里不证明“最大流最小割定理”,简单解释求最大流的Ford-Fulkerson算法。接下来分别详述时间复杂度为O(VE2)的Edmonds-Karp算法和时间复杂度为O(V2E)的Dinic算法。至于较新的预留推进算法就不介绍了,这个算法证明比较难,感兴趣的可以看看算法导论。

本文所用到的网络流如图1,s为原点,t为汇点,边上的值表示边的容量c(u,v),如c(s,v1)=15,c(v1,v2)=8。流用符号f(u,v)表示,如图2,流的容量f(s,v1)=10,f(v1,v2)=5。剩余容量cf(u,v)=c(u,v)-f(u,v)。在原剩余网络中找到一条流后,修改原网络边的剩余容量得到剩余网络Gf,如图3所示。注意剩余网络中有一些新添加的边即反向边的容量,为流的反馈。在图3中,有f(v1,v3)=5,那么有f(v3,v1)=-f(v1,v3)=-5,然后cf(v3,v1)=c(v3,v1)-f(v3,v1)=0-(-5)=5。


图1 网络流的一个例子


1.Ford-Fulkerson算法

算法的主要思想:

1)初始化一条容量为0的流f和一个剩余网络Gf,第一个剩余网络为原图G,每条边的剩余容量初始化为每条边的初始容量cf(u,v)=c(u,v)。

2)在剩余网络Gf中寻找增广路径p,取增广路径p中的边的剩余容量cf(u,v)最小值作为流的增量△f,使得f’=f+△f。修改剩余图中每条边的容量cf’(u,v)=cf(u,v)- △f得到剩余网络Gf。(补充说明:原算法中的△f可以为单位1)

3)重复步骤2),直到找不到一条增广路径为止。

寻找一条增广路径的方法如图4所示,然后确定增广路径上的流增量△f。本例中△f=3


整个网络可以用邻接表或矩阵表示,网络的边即为矩阵中的元素,网络的节点则为矩阵一维的下标。这里网络中每条边最好用一个结构体表示。结构体包含边的容量和每次通过的流量这两个变量。

下面是一个例子:





Ford-Fulkerson算法的伪代码如下:

Ford-Fulkerson(G, st)

1                    for eachedge (uv) Î E[G]          //初始化每条边的流量为0

2                     {          f[uv]¬ 0

3                        f[vu]¬ 0

4                     }

5                    //Gf  ¬ G                                      //初始化剩余网络Gf为原网络G,这里不需要代码

6                    while thereexists a path p froms totin the network Gf   //网络中还存在增广路径,仍然进行迭代

7                     {search a pathp from network Gf                //Karp算法采用广度优先,Dinic算法采用深度优先

8                       cf (p) ¬ Min{ cf (u,v) | (u, v) is inp}            //确定增广路径上的流增量△f(p)= cf (p)

9                               for eachedge (u, v) inp                                           

10                       {      f[uv]¬ f[u,v] +cf (p)                  //增加剩余网络中增广路径上每条边的流量

11                                    f[vu]¬ - f[u,v]                            //显然该路径上反方向上的容量为负

12                                    c[u, v] ¬ c[u,v] –f[u, v]               //计算剩余网络Gf中的每条边的容量

13                                    c[v, u] ¬ c[v,u] –f[v, u]

14                             }

15                  }

 

2. Edmonds-Karp算法

Edmonds-Karp算法与Ford-Fulkerson算法的区别在于在Ford-Fulkerson算法的第7行,Edmonds-Karp算法采用广度优先算法(BFS)寻找一条从st最短增广路径p代替Ford-Fulkerson的随机寻找一条从st增广路径p

引理1:在网络G=<V,E>中,原点为s,汇点为t。Edmonds-Karp算法中,对于任意顶点v ÎV – {s,t},在剩余网络Gf中的距离df(s,v)和 df (v,t)随着流的增加而单调递增。(每次增加两个单位,这里要用到BFS生成最短路径的性质,由于这次的增广路径在剩余网络中已经是最短路径了,在新的剩余网络中,通过(s, v)的最短路径要增加。证明略)

引理2:流增加的总次数不超过O(VE)。

证明(1):若在剩余图Gf中的边(u, v)满足c(u,v)=cf(u, v),边(u, v)是一条关键边。每次进行增广路径扩充后,关键边(u, v)不会在该次的剩余网络Gf中出现。每次扩充至少会有一条关键边。可以证明网络中的每条边称为关键边至多|V|/2-1次。所以流增加的总次数为O(VE)。

证明(2):当边(u, v)在上一次剩余网络Gf中第一次称为关键边时,有d(sv)= df (su) + 1成立。然后边(u, v)将不会在该次剩余网络Gf’中,边(u, v)下一次出现在某个剩余网络中的时候有,肯定有流通过边(v, u)。假设当这种情况发生时,有网络Gf’’的流为f,我们有:

其中由引理1: df ‘ (sv)³df  (s,v),有

df’ (s,u)=df ‘ (s,v)+1³d(s,v)+1=d(s,u)+2

所以从s到u的路径中,当其中的一条边称两次为关键边的时候,s到u的距离增加2个单位。s到v的距离最长为n-1,那么s到u的最长距离为n-2。所以边(u, v)能成为关键边的次数最多为n/2-1次。所以流增加的总次数为O(VE)。

Edmonds-Karp算法的时间复杂度:O(VE2)

由引理2可知,流增加的总次数不超过O(VE),又每次扩充增广路径的时间复杂度为O(E),故Edmonds-Karp算法的时间复杂度为O(VE2)。

3.Dinic算法

Dinic算法要用到层次图的数据结构,即MSN(Multi-Stage Network)。MSN可由每次计算得到的剩余网络Gf再计算得到。

定义1:k-阶图,k-stage图(或网络)G = (V, E)是一个有向图,G的顶点集合被分成(k+1)³ 2个不相交的集合Vi,i £k。如果有边(u, v)∈E,则有uΠViv Î Vi+1,对某个i∈[0,k-1]成立。V0=sVk=tk-阶图和k-阶图中的一条阻塞流如下图所示。图中6产生了一条阻塞流表示在该图上从原点s到汇点t再不可能增加新的流了。

定义2:饱和边,如果边(u, v)满足f(u, v)=c(u, v)。

定义3:流称为网络G的阻塞流,当且仅当从st的任何一条路径中都包含一条饱和边。


图6  k-阶图

 

Dinic算法中用到的MSN数据结构(多阶网络)如下图8所示。MSN可以在剩余网络Gf中用BFS算法构建,时间复杂度为O(|V|+|E|)。要得到Dinic算法使用的MSN还需要一些简答的处理,如图7中,利用BFS得到的MSN可能含有边不能到汇点t,所以在计算MSN的过程中需要将这种边除去。如图8中的边(v3, x)。


图7 一个剩余网络Gf


图8  剩余网络图7Gf的一个MSN

 

引理1:Dinic算法中每次迭代后,MSN的阶数至少增加2。

假设流是一个由Gf计算得到的MSNf的阻塞流,f’=f+f*,令MSNf和MSNf 为Gf和Gf计算得到的MSN。df (s, t)和df (s, t)分别是在MSNf和MSNf 上从st的最短路径。我们仅仅需要证明df (s, t)=df (s, t)+2,除非t不在MSN中。考虑任何一条从st 的路径p,明显有|p|=df (s, t)。如果我们假定路径p上所有的边都在MSNf上,那么流f*将不是一条阻塞流,因为我们可以继续增大在MSNf上的路径p的流f的容量,使得MSNf上的路径p的边至少有一条边成为饱和边,那么该边将不会在MSNf上出现。所以这里必有一条边(u, v)将不会在MSNf上出现。令边(u, v)是在MSNf上出现而不会在MSNf上出现的边。当边(u, v)再次出现的情况是因为边(v, u)成为增广路径p’上的一条饱和边。这意味着有df (s, v)=df (s, u)+1;df (s, u)=df(s, v)+1。因为边(u, v)在路径p上,由第2节中的引理1可知,df(s, u) £df’ (s, u)和df(s, v) £df’  (s, v)成立。所以我们得到

d(s, t)= d(s, v)+ df(v,t)

= d(s, u) +1+d(v,t)

=( d(s, v)-1)+1+(d(u,t)-1)

=d(s, v)+1+d(u,t)-2

£df’  (s, v)+1+df’  (u, t)-2

=df’  (s, v)+df’  (u, t)-1

=df’  (s, v)+( df’  (v, t)-1)-1

=df’  (s, t)-2

引理2:Dinic算法迭代的轮数至多为,即计算MSN的次数为O(V)。

第一个MSN的计算式从流f初始化为0开始的(见后面算法伪代码),用Dinic算法又计算了MSN k次。由引理1可知,1+2k≦n-1,因为任何路径的长度,包括df (s, t)都小于或等于n-1,所以k £ ën/2û - 1,引理得证。

引理3:Dinic算法每次计算阻塞流的时间复杂度为O(VE)。

阻塞流的寻找可以采用DFS算法。在MSN中从源点s出发寻找一条阻塞流*,初始化*=0。我们利用栈Stack来构建DFS算法寻找一条阻塞流,压栈到最深的一个节点后,然后当一个节点v从栈Stack中Pop出来后,分两种情况讨论:

1)v=t

因为存储在栈Stack中的顶点序列从栈底到栈顶为一条从st的路径,也是MSN中从st的一条最短路径p。令cf(p)为路径p的容量。在剩余网路Gf中增加路径p上的流f*的容量至cf(p),然后计算新的MSNf。对于在MSNf中的路径p的每条边(u, v),减去容量cf(p)。如果该边的容量减少至0后,就将该边标记成饱和边。在MSNf中这条边就不存在了,相反在Gf中会有一条反向的边出现。

2)v≠t

这意味着顶点v不能到达汇点t,也即是栈中的路径从s只能最终到达v不能到达t,需要退栈。从新选择新的边压栈。

每次DFS的时间为O(n),因为在MSNf中路径的长度最多为n-1,每次压栈到顶点t后,开始出栈,修改边的容量,若该边为饱和边则在MSNf中去掉该边。然后,继续退栈,寻找新的边入栈新的顶点,如此反复进行DFS收索直到一条阻塞流f*产生。实际情况中阻塞流f*产生后,MSNf中已经没有从源点s到达汇点t的边了。所以在MSNf中利用DFS寻找一条阻塞流f*的时间复杂度为O(VE)。

引理4:Dinic算法的时间复杂度为O(V2E)。

由引理2和引理3可知Dinic算法的时间复杂度为O(V2E)。

Dinic算法伪代码

Dinic (G, st)

1        for eachedge (uv) Î E[G]

2        {         f[uv]¬ 0

3                   f[vu]¬ 0

4        }

5        Gf  ¬ G

6        Compute the MSN for Gf starting from source s

7        while sink t is in MSN

8         {           find a blocking flow f* in MSN

9                      for eachedge (uv) in G 

10                   {       f[uv]¬ f[u,v] +f*[u,v]    }

11                   ComputeGf for flow f

12                   Re-compute MSNfor Gf

13      }

14     End

 

下面是Edmonds-Karp算法的一个例子

 

原网络






 

最后举一个Dinic算法的例子


原网络G,也是第一个剩余网络Gf







从上面的例子可以看出,Dinic算法迭代计算了2次,而Karp算法迭代计算了3次。

模板代码:

#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
const int Ni = 210;
const int MAX = 1<<26;
struct Edge
{
    int u,v,c;
    int next;
} edge[20*Ni];
int n,m;
int edn;//边数
int p[Ni];//父亲
int d[Ni];
int sp,tp;//原点,汇点

void addedge(int u,int v,int c)
{
    edge[edn].u=u;
    edge[edn].v=v;
    edge[edn].c=c;
    edge[edn].next=p[u];
    p[u]=edn++;
    edge[edn].u=v;
    edge[edn].v=u;
    edge[edn].c=0;
    edge[edn].next=p[v];
    p[v]=edn++;
}
int bfs()
{
    queue <int> q;
    memset(d,-1,sizeof(d));
    d[sp]=0;
    q.push(sp);
    while(!q.empty())
    {
        int cur=q.front();
        q.pop();
        for(int i=p[cur]; i!=-1; i=edge[i].next)
        {
            int u=edge[i].v;
            if(d[u]==-1 && edge[i].c>0)
            {
                d[u]=d[cur]+1;
                q.push(u);
            }
        }
    }
    return d[tp] != -1;
}
int dfs(int a,int b)
{
    int r=0;
    if(a==tp)return b;
    for(int i=p[a]; i!=-1 && r<b; i=edge[i].next)
    {
        int u=edge[i].v;
        if(edge[i].c>0 && d[u]==d[a]+1)
        {
            int x=min(edge[i].c,b-r);
            x=dfs(u,x);
            r+=x;
            edge[i].c-=x;
            edge[i^1].c+=x;
        }
    }
    if(!r)d[a]=-2;
    return r;
}

int dinic(int sp,int tp)
{
    int total=0,t;
    while(bfs())
    {
        while(t=dfs(sp,MAX))
            total+=t;
    }
    return total;
}
int main()
{
    int i,u,v,c;
    while(~scanf("%d%d",&m,&n)) //m为输入的边数,n为顶点数
    {
        edn=0;//初始化
        memset(p,-1,sizeof(p));
        sp=1;
        tp=n;
        for(i=0; i<m; i++)
        {
            scanf("%d%d%d",&u,&v,&c);
            addedge(u,v,c);
        }
        printf("%d\n",dinic(sp,tp));
    }
    return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值