最大流算法(Edmons-Karp + Dinic 比较) + Ford-Fulkson 简要证明

Ford-Fulkson用EK实现:483ms

#include <cstdio>
#include <cstring>
#define min(x,y) (x>y?y:x)
int pre[105],q[105];
int F[105][105];
int n,nc,np,m,s,t,all;
int MaxFlow(int s, int t){
    int ans=0;
    while(1){
        memset(pre,0,sizeof(pre));
        int head=0,tail=0;
        q[++tail]=s;
        while(head<tail&&pre[t]==0){
            int cur=q[++head];
            for(int i=1; i<=n; i++)
            if(F[cur][i]>0&&pre[i]==0){
                pre[i]=cur;
                q[++tail]=i;
            }
        }
        if(pre[t]==0) break;
        int minF=100000000;
        for(int i=t; i!=s; i=pre[i])
            minF=min(minF,F[pre[i]][i]);
        for(int i=t; i!=s; i=pre[i])
        {
            F[pre[i]][i]-=minF;
            F[i][pre[i]]+=minF;
        }
        ans+=minF;
    }
    return ans;
}
int main()
{
    int x,y,z;
    while(scanf("%d%d%d%d",&n,&np,&nc,&m)!=EOF){
        all=0;
        memset(F,0,sizeof(F));
        s=n+1;
        t=n+2;
        n=n+2;
        for(int i=0; i<m; i++){
            while(getchar()!=‘(‘) ;
            scanf("%d,%d)%d",&x,&y,&z);
            F[x+1][y+1]=z;
        }
        for(int i=0; i<np; i++){
            while(getchar()!=‘(‘) ;
            scanf("%d)%d",&x,&z);
            F[s][x+1]=z;
        }
        for(int i=0; i<nc; i++){
            while(getchar()!=‘(‘) ;
            scanf("%d)%d",&x,&z);
            F[x+1][t]=z;
        }
        printf("%d\n",MaxFlow(s,t));
    }
    return 0;
}

Dinic用DFS实现:360ms

#include <cstdio>
#include <cstring>
#define min(x,y) ((x)>(y)?(y):(x))
#define N 105
#define M 10005
int n,np,nc,m,all,s,t;
int d[N],be[N];
struct Edge{
    int x,y,c,next;
}e[M*2];
void add(int x, int y, int z)//需保证相反边第一个为偶数
{
    e[all].x=x; e[all].y=y; e[all].c=z;
    e[all].next=be[x];
    be[x]=all;
    all++;
    e[all].x=y; e[all].y=x; e[all].c=0;
    e[all].next=be[y];
    be[y]=all;
    all++;
}
bool BFS(int s, int t)
{
    memset(d,-1,sizeof(d));
    int head=0,tail=0,q[N];
    q[++tail]=s;
    d[s]=0;
    while(head<tail){
        int cur=q[++head];
        for(int i=be[cur]; i!=-1; i=e[i].next)
        if(e[i].c>0 && d[e[i].y]==-1){
            d[e[i].y]=d[cur]+1;
            q[++tail]=e[i].y;
        }
    }
    return d[t]!=-1;
}
int DFS(int cur, int minc)
{
    if(cur==t) return minc;//表示路径上该点前方最小容量
    int ans=0,tmp;//ans表示该点要增大的流
    for(int i=be[cur]; i!=-1; i=e[i].next)
    if(e[i].c>0 && d[e[i].y]==d[cur]+1 && (tmp=DFS(e[i].y,min(minc-ans,e[i].c))))//多路增广很快 因为不用返回到s 容易证明能保证minc>0
    {
        e[i].c-=tmp;
        e[i^1].c+=tmp;
        ans+=tmp;
    }
    if(ans==0) d[cur]=-1;//关键的一句话 否则超时 用于多路增广
    return ans;
}
int Dinic(int s, int t)
{
    int ans=0,tmp;
    while(BFS(s,t)){
        while(tmp=DFS(s,1000000000))
            ans+=tmp;
    }
    return ans;
}
int main()
{
    int x,y,z;
    while(scanf("%d%d%d%d",&n,&np,&nc,&m)!=EOF){
        all=0;
        memset(be,-1,sizeof(be));
        s=n;
        t=n+1;
        n=n+2;
        for(int i=0; i<m; i++){
            while(getchar()!=‘(‘) ;
            scanf("%d,%d)%d",&x,&y,&z);
            add(x,y,z);
        }
        for(int i=0; i<np; i++){
            while(getchar()!=‘(‘) ;
            scanf("%d)%d",&x,&z);
            add(s,x,z);
        }
        for(int i=0; i<nc; i++){
            while(getchar()!=‘(‘) ;
            scanf("%d)%d",&x,&z);
            add(x,t,z);
        }
        printf("%d\n",Dinic(s,t));
    }
    return 0;
}

Dinic用栈模拟递归实现:63ms

#include <cstdio>
#include <cstring>
#define min(x,y) ((x)>(y)?(y):(x))
#define N 105
#define M 10005
int n,np,nc,m,all,s,t;
int d[N],be[N];
struct Edge{
    int x,y,c,next;
}e[M*2];
void add(int x, int y, int z)//需保证相反边第一个为偶数
{
    e[all].x=x; e[all].y=y; e[all].c=z;
    e[all].next=be[x];
    be[x]=all;
    all++;
    e[all].x=y; e[all].y=x; e[all].c=0;
    e[all].next=be[y];
    be[y]=all;
    all++;
}
bool BFS(int s, int t)
{
    memset(d,-1,sizeof(d));
    int head=0,tail=0,q[N];
    q[++tail]=s;
    d[s]=0;
    while(head<tail){
        int cur=q[++head];
        for(int i=be[cur]; i!=-1; i=e[i].next)
        if(e[i].c>0 && d[e[i].y]==-1){
            d[e[i].y]=d[cur]+1;
            q[++tail]=e[i].y;
        }
    }
    return d[t]!=-1;
}
int Dinic(int s, int t)//防止爆栈 用stack模拟递归
{
    int ans=0;
    int stack[N],top;
    int begin[N];
    while(BFS(s,t))
    {
        memcpy(begin,be,sizeof(be));
        int cur=s;
        top=0;//dfs开始 清空栈
        while(1)
        {
            if(cur==t){
                int minc=1000000000,mini;
                for(int i=0; i<top; i++)
                if(minc>e[stack[i]].c)
                {
                    minc=e[stack[i]].c;
                    mini=i;//以便之后回到这继续增广
                }
                for(int i=0; i<top; i++)
                {
                    e[stack[i]].c-=minc;
                    e[stack[i]^1].c+=minc;//第一个二进制取反 即取相反边
                }
                ans+=minc;
                top=mini;
                cur=e[stack[mini]].x;
            }
            for(int i=begin[cur]; i!=-1; begin[cur]=i=e[begin[cur]].next)
                if(e[i].c>0 && d[e[i].y]==d[e[i].x]+1) break;
            if(begin[cur]!=-1){
                stack[top++]=begin[cur];
                cur=e[begin[cur]].y;
            }else{
                if(top==0) break;
                d[cur]=-1;//当前节点不在增广路中 删除
                cur=e[stack[--top]].x;//回溯
            }
        }
    }
    return ans;
}
int main()
{
    int x,y,z;
    while(scanf("%d%d%d%d",&n,&np,&nc,&m)!=EOF){
        all=0;
        memset(be,-1,sizeof(be));
        s=n;
        t=n+1;
        n=n+2;
        for(int i=0; i<m; i++){
            while(getchar()!=‘(‘) ;
            scanf("%d,%d)%d",&x,&y,&z);
            add(x,y,z);
        }
        for(int i=0; i<np; i++){
            while(getchar()!=‘(‘) ;
            scanf("%d)%d",&x,&z);
            add(s,x,z);
        }
        for(int i=0; i<nc; i++){
            while(getchar()!=‘(‘) ;
            scanf("%d)%d",&x,&z);
            add(x,t,z);
        }
        printf("%d\n",Dinic(s,t));
    }
    return 0;
}

之前一题poj1459是经典最大流题目,倒是想要回顾一下,顺便将书中证明简要摘出:(哈哈)

而网络流的增广路与二分图还是有相当的差别的,那么如何来判断网络流的流量最大呢,那么得清楚如何使得当前网络流的流量再次增大,首先显而易见的就是直接用bfs找出前进路径中所能增加的最大流量,这个是毋庸置疑的.只有这种情况吗?我们只考虑了前向边,而后向边得想想是否能够增加的余地.

先看看这个图 (时间不够先粗略画这)

仔细看s-1-2-t,是否能增加,用s-1的流量顶替2-1的流量后2-t的流量自然增加,

故我们只需不断的找出这两种增广路径,然后更新即可.

因此:

(前向边:路径上边方向是从s指向t的边;后向边:路径上边方向从t指向s的边)

对于路径上所有前向边(u,v)满足f(u,v)<c(u,v)且所有后向边(v,u)满足f(v,u)>0,则该路径称为增广路径.

证明:只要当前网络中不存在从s到t的增广路径,则当前流为最大流.

要证明这个定理,我们得先引入最大流最小割定理.

割的概念:对于网络流图D=(V,E,C)的所有节点集,将该节点集分为集合S和T,且其中源点s∈集合S,汇点t属于T,对于图上满足u∈S,v∈T的所有边(u,v)的集合称为割,其中割中每条边的容量和称为割的容量.而容量最小的割称为最小割.

最大流最小割定理即:在一个给定的网络流图上,最大流等于最小割的容量.

证明:

设网络流图D的网络流f使得流量达到最大.

定义割(S,T):

1.源点s∈S

2.若u∈S,且f(u,v)<c(u,v),则v∈S

3.若u∈S,且f(v,u)>0,则u∈S

显然,源点t∈T,否则若t∈S,则由割的定义,必然从s到t中存在一条增广路径,则与f为最大流矛盾,因此t∈T.故从s到t间的所有路径必然都需要从S到T即需要经过割中的边,又由割(S,T)的定义,f(u,v)=c(u,v),c(v,u)=0因此sigama(f)=sigama(c(u,v)-c(v,u))=sigama(c(u,v))(其中u∈S,v∈T),即最大流的总流量等于割(S,T)的边容量,现在只需证明割(S,T)为最小割.

假设割(S,T)不为最小割,则f就不是最大流,因为总流量必然小于最小割的容量,因为从s到t的所有路径必然需要经过从S到T的过程,故总流量必然小于任意S与T的割的容量,即小于最小割的容量,故f非最大流,矛盾,故割(S,T)为最小割

得证.

而在最大流的情况下,最小割(即上述割(S,T))的定义,即无增广路径.

因此只要当前网络中不存在从s到t的增广路径,则当前流为最大流.

而至于流程我们只需重复进行:

  1.找增广路径,找出最大可增量a=min(前向边c(u,v)-f(u,v),后向边f(v,u))

  2.修改增广路径上每条前向边f(u,v)+a;后向边f(v,u)-a;

原来Ford-Fulkerson即上面1~2的思想,是用来求最大流的“方法”,若朴素查找增广路即一次增加1流量,复杂度为O(N*F)

而通过广搜来实现查找增广路径的“算法”是Edmonds-Karp算法O(N*M^2)

而Dinic算法,每次更新层次图广搜一遍O(M),而进行增广时每增广一次删除一个点,最多删除n个点,因此为O(N^2),因此总复杂度为O(N^2*M)

最大流算法(Edmons-Karp + Dinic 比较) + Ford-Fulkson 简要证明,布布扣,bubuko.com

时间: 2024-12-15 06:53:20

最大流算法(Edmons-Karp + Dinic 比较) + Ford-Fulkson 简要证明的相关文章

Ford-Fulkerson 最大流算法

流网络(Flow Networks)指的是一个有向图 G = (V, E),其中每条边 (u, v) ∈ E 均有一非负容量 c(u, v) ≥ 0.如果 (u, v) ∉ E 则可以规定 c(u, v) = 0.流网络中有两个特殊的顶点:源点 s (source)和汇点 t(sink).为方便起见,假定每个顶点均处于从源点到汇点的某条路径上,就是说,对每个顶点 v ∈ E,存在一条路径 s --> v --> t.因此,图 G 为连通图,且 |E| ≥ |V| - 1. 下图展示了一个流网络

Cable TV Network 顶点连通度 (最大流算法)

Cable TV Network 题目抽象:给出含有n个点顶点的无向图,给出m条边.求定点联通度   K 算法:将每个顶点v拆成 v'   v''  ,v'-->v''的容量为1.           对于原图中的边(u,v)   连边   u''--->v'    v''-->u'.    求每对定点的P(u,v);以u为源点,v为汇点. 我们只需固定一个顶点,枚举其它汇点. 1 #include <iostream> 2 #include <cstdio> 3

算法9-4:最大流算法复杂度分析

前面一节介绍了Ford-Fulkerson算法.那么这个算法是否一定能够在有限步骤内结束?要多少步骤呢? 这个问题的答案是,该算法确实能够在有限步骤之内结束,但是至于需要多少步骤,就要仔细分析. 为了分析问题,需要假定图中所有边的容量都是整数.但是有个严重的问题,比如下图中,如果使用Ford-Fulkerson算法,需要迭代200次才能结束. 首先将所有边的容量都初始化为0. 第一次迭代和第二次迭代之后,两条边各增加了1. 到最后200次迭代之后整个算法才结束. 这还不算最坏的情况.因为整数最多

算法9-5:最大流算法的Java代码

残留网络 在介绍最大流算法之前先介绍一下什么是残留网络.残余网络的概念有点类似于集合中的补集概念. 下图是残余网络的例子.上面的网络是原始网络,下面的网络是计算出的残留网络.残留网络的作用就是用来描述这个网络中还剩下多少可以利用的流量. 流量网络 最大流算法比以前介绍的算法都要复杂.网络中的每一条边需要记录容量和当前流量.容量是固定值,是已知条件,而当前流量在计算过程中会一直发生变化.因此,需要建立一个专门的类,用于最大流算法. public class FlowEdge { private i

poj 2391 Ombrophobic Bovines, 最大流, 拆点, 二分, dinic

poj 2391 Ombrophobic Bovines, 最大流, 拆点, 二分 dinic /* * Author: yew1eb * Created Time: 2014年10月31日 星期五 15时39分22秒 * File Name: poj2391.cpp */ #include <ctime> #include <cmath> #include <cstdio> #include <cstdlib> #include <cstring&g

接口限流算法总结

背景 曾经在一个大神的博客里看到这样一句话:在开发高并发系统时,有三把利器用来保护系统:缓存.降级和限流.那么何为限流呢?顾名思义,限流就是限制流量,就像你宽带包了1个G的流量,用完了就没了.通过限流,我们可以很好地控制系统的qps,从而达到保护系统的目的.本篇文章将会介绍一下常用的限流算法以及他们各自的特点. 算法介绍 计数器法 计 数器法是限流算法里最简单也是最容易实现的一种算法.比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个.那么我们可以这么做:在一开 始的时候,我们可

常用的限流算法

常用的限流算法大致有三种:令牌桶算法,漏桶算法,计数器算法 令牌桶算法 令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌.令牌桶算法的描述如下: 1.假设限制2r/s,则按照500毫秒的固定速率往桶中添加令牌 2.桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝 3.当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上 4.如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待) 漏桶算法 漏桶作为计量工具(The

限流算法

常见的限流算法有:令牌桶.漏桶.计数器. 令牌桶限流 令牌桶是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌,填满了就丢弃令牌,请求是否被处理要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求.令牌桶允许一定程度突发流量,只要有令牌就可以处理,支持一次拿多个令牌.令牌桶中装的是令牌. 漏桶限流 一个固定容量的漏桶,按照固定常量速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝.漏桶可以看做是一个具有固定容量.固定流出速率的队列,漏桶限制的是请求的流出速率

高并发限流算法

开篇 在高并发系统中,有很多手段来保护系统,如缓存.降级和限流等. 缓存:让数据尽早进入缓存,离程序近一点,不要大量频繁的访问DB,可提供系统访问速度和增大系统处理能力. 降级:当服务出问题或者影响到核心流程的性能,需要将服务暂时屏蔽掉,待高峰期过去或问题解决后再启用. 然后,有些场景不能用缓存和降级来解决.比如电商的双十一,用户的购买,下单等行为,是涉及到大量写操作,而且是核心链路,无法降级的. 限流:通过把并发访问/请求进行限速或者一定时间窗口内的请求限制在一定范围内来保护系统,一旦达到限制