网络流最大流的概念及Edmonds-Karp算法的讲解

网络流,一类听起来就很高大上的令无数OI选手胆颤的图论算法.

这里只讲解神奇的最大流问题.

最大流,就是一类在有向图上的问题.

这张有向图比较特殊,有一个源点,一个汇点.

且每条边都有一个容量.

最大流问题是指,从源点开始流,最多有多大的权值能流到汇点的一类图论问题.

网络图如下:


最大流其实就是把水灌入一条边之后,相当于把这条边的容量减少了,最终能流到的流量最大就是最大流问题的解.

我觉得我还是解释不清楚网络流,好好的给你们用公式说吧.

一张网络G={V,E},我们定义边(x,y)的权值为c(x,y),称为容量,且若不存在(x,y)∈E,则c(x,y)=0.且有两个节点S,T∈V,S称为源点,T称为汇点.

其中f(x,y)是一个流函数,表示边(x,y)的流量,且f(x,y)<=c(x,y),c(x,y)-f(x,y)为边的剩余容量.

整个网络流量为所有(S,v)∈E,f(S,v)的和.

整个网络流量的最大流量为最大流.

至于算法,我们这里介绍Karp算法.

karp算法比较简单的思想,就是一直不停地找增广路.

增广路的定义:网络图G中,从源点到汇点的一条路上最小容量边的容量>0简单路径.

karp算法就是不断找到增广路,找到一条后将整条路上流量最小的minf求出,增加到最大流的答案中,再将整条增广路上的剩余容量求出,继续跑.

一般来说karp算法的找增广路用bfs,因为bfs更加稳定.

但是若真的直接这样模拟,会出问题,例如:


若直接沿着红色的路径走,最大流就是1,然而这张网络的最大流明显是2.

所以我们应该怎么处理呢?

若我们把反向边加进去:


那么就不会错了.

为什么呢?

因为我们这样相当于让程序有了一个反悔的机会.

通俗地说,就是你往反向边跑了过去,就相当于重新将这条边的容量给加回来了.

所以karp算法的代码如下:

struct queue{
  int x,minf,last,edge;
}q[2*N+1];
bool use[N+1];
int h,t;
int bfs(int S,int T){
  memset(use,0,sizeof(use));
  h=t=1;
  q[t].x=S;q[t].minf=INF;q[t].last=0;use[S]=1;
  while (h<=t){
    for (int i=lin[q[h].x];i;i=e[i].next)
      if (!use[e[i].y]&&e[i].v){      //边必须还存在 
      	use[e[i].y]=1;
      	q[++t].x=e[i].y;
      	q[t].minf=min(q[h].minf,e[i].v);      //更新增广路上最小容量边的容量 
      	q[t].last=h;      //记录上一个状态的位置 
      	q[t].edge=i;      //记录边的编号 
        if (q[t].x==T) {
          for (int j=t;q[j].last;j=q[j].last){      //更新增广路上的剩余容量 
      	    e[q[j].edge].v-=q[t].minf;      //边的容量减小 
            e[q[j].edge^1].v+=q[t].minf;      //反向边的容量增大 
          }
          return q[t].minf;      //返回这条增广路 
        }
      }
    ++h;
  }
  return 0;      //没有增广路,返回假 
}
int maxflow(int S,int T){      //源点为S,汇点为T的最大流
  int sum=0;
  while (1){      //用while (1)控制循环
    int minf=bfs(S,T);      //做一遍bfs 
    if (minf) sum+=minf;
    else break;
  }
  return sum;
}

注意,这种写法的成对储存要从第2条便开始存,也就是说,e[1]是不存在的.

时间复杂度O(n*m^2),但是上界很松,一般可以处理10^3~10^4级的数据.

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/80441477