图论五:网络流
参考文章一:https://blog.csdn.net/txl199106/article/details/64441994
参考文章二:https://blog.csdn.net/ergedathouder/article/details/55001645
一、基本概念
1、源点:只进不出的点,通常称为S
2、汇点:只出不进的点,通常称为T
3、容量&流量:容量是每条边上最大能经过的值,流量是当前经过边的值。(流量一定小于容量)
容量用C表示,流量用F表示。
4、三个基本性质:
(1)每条边的流量小于容量,即C<F
(2)流量守恒:Σ F<v,x> = Σ F<x,u>
(3)斜对称性:F<x,y> = - F<y,x>
5、容量网络&流量网络&残留网络
残留网路 = 容量网络 - 流量网络
6、割集:(可以分为点割集和边割集)
就是去掉这一部分图不连通的部分。
(最小割集是能成为割集的最小集合)
7、最大流最小割定理
(1)任意一个流都小于一个割;
(2)将满流边作为一个割,就构造出和最大流相等的割;
(3)最大流等于最小割
设相等的流和割分别为Fm,Cm,
则有:F<=Fm=Cm<=C(C,F为任意的)。
三个等价条件:
对于一个图G=(V,E)有源点s和汇点t
(1)F是图G最大流
(2)残留网络GF不存在增广路径,
(3)对于G的一个割(S,T),存在f=C(S,T)。
二、算法实现
1、增广路径算法(EK算法)
(1)算法功能:求增广路径
(2)算法思想:bfs
(3)算法流程:
首先建立邻接表,存储每条边的数据,pre数组记录路径,vis数组标记路径,接着对数据进行初始化;
其次,bfs遍历寻找最小边,然后更新整个图,建立反向边,并求和,更新ans的值,之后一直重复此操作。
最后,如果bfs遍历时找不到最后的节点,说明图中没有增广路径,算法结束。
代码:
(1)邻接数组实现
#include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; const int maxn = 1200; const int INF = 0x3fff; int pre[maxn],p[maxn],vis[maxn],edge[maxn][maxn],m,n; int MIN(int x,int y){ return x<y?x:y; } void Init() { memset(edge,0,sizeof(edge)); } int EK(int st,int ed) { int ans=0,j,i,fg,mi; while(1) { fg=0; queue <int> q; q.push(st); memset(vis,0,sizeof(vis)); memset(pre,0,sizeof(pre)); p[st]=INF; while(!q.empty()) //寻找增广路径 { int top=q.front(); q.pop(); for(i=1;i<=n;i++) if(edge[top][i]&&vis[i]==0) { vis[i]=1; pre[i]=top; //记录增广路径 if(i==ed){ fg=1;break; } q.push(i); } } if(fg==0) return ans; mi=INF; for(i=ed;i!=st;i=pre[i]) mi=MIN(mi,edge[pre[i]][i]); //寻找最小值 for(i=ed;i!=st;i=pre[i]) edge[pre[i]][i]-=mi,edge[i][pre[i]]+=mi; //更新路径 ans+=mi; } } int main(void) { int x,y,z; cin>>n>>m; Init(); for(int i=1;i<=m;i++) cin>>x>>y>>z,edge[x][y]+=z; cout<<EK(1,n)<<endl; return 0; }
(2)邻接表实现
#include<iostream> #include<cstdio> #include<queue> #include<cstring> using namespace std; const int maxn = 1200; const int INF = 0x3fff; struct Edge{ int v,next,w; //v表示边的终点,next表示下一个边的编号 }edge[maxn]; struct Vertex{ int v,id; //v表示下一个点,id表示当前的点 }pre[maxn]; int n,m,cnt; int vis[maxn],head[maxn]; int MIN(int x,int y){ return x<y?x:y; } void Init() //初始化 { cnt=0; memset(edge,0,sizeof(edge)); memset(head,-1,sizeof(head)); } void add(int u,int v,int w) //添加新边 { edge[cnt].v=v; edge[cnt].w=w; edge[cnt].next=head[u]; head[u]=cnt++; } int EK(int st,int ed) //EK算法具体实现 { int i,j,fg,tmp,ans=0,mi; while(1) { fg=0;st=1;ed=n; memset(vis,0,sizeof(vis)); memset(pre,-1,sizeof(pre)); pre[st].v=st;vis[st]=1; queue <int> q; q.push(st); while(!q.empty()) { int top=q.front(); q.pop(); for(i=head[top];i+1;i=edge[i].next) { int v=edge[i].v; if(vis[v]==0&&edge[i].w) { vis[v]=1; pre[v].v=top; pre[v].id=i; if(v==ed) { fg=1;break; } q.push(v); } } } if(fg==0) return ans; mi=INF; for(i=ed;i!=st;i=pre[i].v) mi=MIN(mi,edge[pre[i].id].w); //正向路径更新 for(i=ed;i!=st;i=pre[i].v) edge[pre[i].id].w-=mi,edge[pre[i].id^1].w+=mi; //a^1表示取反,即pre[i].id的反向路径更新 ans+=mi; } } int main(void) { int x,y,z; cin>>n>>m; Init(); for(int i=1;i<=m;i++) { cin>>x>>y>>z; add(x,y,z);add(y,x,0); } cout<<EK(1,n)<<endl; return 0; } /* 4 5 1 2 2 1 4 2 2 4 3 2 3 4 4 3 4 */
2、dinic算法
算法流程:
(1)初始化,计算出剩余图
(2)根据剩余图计算层次图,若汇点不在层次图中,算法结束
(3)层次图内用dfs计算增广路径
(4)返回(2)
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; const int maxn = 1200; const int INF = 0x3ffffff; struct Node{ int v,c,next; }edge[maxn*maxn<<1]; int level[maxn],p[maxn],n,m,S,T,cnt; void Init() { memset(p,-1,sizeof(p)); cnt=0;S=1;T=n; } int MIN(int x,int y) { return x<y?x:y; } void Insert(int u,int v,int c) { edge[cnt].v=v; edge[cnt].c=c; edge[cnt].next=p[u]; p[u]=cnt++; } void addedge(int u,int v,int c) { Insert(u,v,c); Insert(v,u,0); } bool bfs() { memset(level,-1,sizeof(level)); level[S]=0; queue <int> q; q.push(S); while(!q.empty()) { int u=q.front(); q.pop(); for(int i=p[u];i!=-1;i=edge[i].next) { int v=edge[i].v; if(edge[i].c>0&&level[v]==-1) { level[v]=level[u]+1; q.push(v); } } } return level[T]!=-1; } int dfs(int u,int cp) { if(u==T) return cp; int res=0; for(int i=p[u];i!=-1;i=edge[i].next) { int v=edge[i].v; if(level[u]+1==level[v]&&edge[i].c>0) { int t=dfs(v,MIN(cp,edge[i].c)); cp-=t; res+=t; edge[i].c-=t; edge[i^1].c+=t; if(cp==0) break; } } if(res==0) level[u]=-1; return res; } int dinic() { int sum=0,tmp; while(bfs()) sum+=dfs(S,INF); return sum; } int main(void) { int i,x,y,z; scanf("%d%d",&n,&m); Init(); for(i=1;i<=m;i++) { scanf("%d%d%d",&x,&y,&z); addedge(x,y,z); } printf("%d\n",dinic()); return 0; } /* 4 5 1 2 2 1 4 2 2 4 3 2 3 4 4 3 4 */