无论是电网、水管网、交通运输网,还是其他的一些网络,都有一个共同点:网络传输都有方向和容量。设有向带权图G=(V,E),V={s,v1,v2,v3,…,t}。在图G中有两个特殊的点s和t,s称为源点,t称为汇点。图中各边的方向表示允许的流向,边上的权值表示该边允许通过的最大可能流量cap,且cap≥0,称为边的容量。而且如果边集合E含有一条边(u,v),必然不存在反方向的边(v,u),称这样的有向带权图为网络。
网络是一个有向带权图,包含一个源点和一个汇点,没有反平行边。
一家郑州电子产品制造公司要把一批货物从工厂(s)运往北京仓库(t),找到一家货运代理公司,代理公司安排了若干货车和运输线路,中间要经过若干个城市,边上的数值代表两个城市之间每天最多运送的产品数量。
一个地下水管网络,我们看不到水在地下管道内是怎么流动的,但是知道从进水口流进去多少水,就从出水口流出来多少水。
网络流:网络流即网络上的流,是定义在网络边集E上的一个非负函数flow={flow(u,v)},flow(u,v)是边上的流量。
可行流:满足以下两个性质的网络流flow称为可行流。
(1)容量约束
每个管道的实际流量flow不能超过该管道的最大容量cap。
(2)流量守恒
除了源点s和汇点t之外,所有内部结点流入量等于流出量。
网络最大流:在满足容量约束和流量守恒的前提下,在流网络中找到一个净输出最大的网络流。
1957年,Ford和Fullkerson提出了求解网络最大流的方法。该方法的基本思想是在残余网络中找可增广路,然后在实流网络中沿可增广路增流,直到不存在可增广路为止。
残余网络:每个网络G及其上的一个流flow,都对应一个残余网络G*。G*和G结点集相同,而网络G中的每条边对应G*中的一条边或两条边。在残余网络中,与网络边对应的同向边是可增量(即还可以增加多少流量),反向边是实际流量。
可增广路是残余网络G*中一条从源点s到汇点t的简单路径。
例如:s—v1—v3—t就是一条可增广路,如图所示。
可增广量是指在可增广路p上每条边可以增加的流量最小值d。
可增广路增流
增流操作分为两个过程:一是在实流网络中增流,二是在残余网络中减流。因为残余网络中可增广路上的边值表示可增量,在实流网络中流量增加了,那么可增量就少了。
实流网络增流:可增广路上同向边增加流量d,反向边减少流量d。
残余网络减流:可增广路上同向边减少流量d,反向边增加流量d。
增广路定理:设flow是网络G的一个可行流,如果不存在从源点s到汇点t关于flow的可增广路p,则flow是G的一个最大流。
增广路算法的基本思想是在残余网络中找到可增广路,然后在实流网络中沿可增广路增流,在残余网络中沿可增广路减流;继续在残余网络中找可增广路,直到不存在可增广路为止。此时,实流网络中的可行流就是所求的最大流。
最短增广路算法
如何找到一条可增广路呢?仁者见仁,智者见智。可以设置最大容量优先,也可以是最短路径(广度优先)优先。Edmonds-Karp算法就是以广度优先的增广路算法,又称为最短增广路算法(Shortest Augument Path,SAP)。
最短增广路算法步骤:
(1)初始化可行流flow为零流,vis[]数组为false,pre[]数组为−1。
(2)令vis[s]=true,s加入队列q。
(3)如果队列不空,继续下一步,否则算法结束,返回最大流。
(4)队头元素new出队,在残余网络中检查new的所有邻接点i。如果未被访问,则访问之,令vis[i]=true,pre[i]=new;如果i=t,说明已到达汇点,找到一条可增广路,转向第(5)步;否则结点i加入队列q,转向第(3)步。
(5)从汇点开始,通过前驱数组pre[],逆向找可增广路上每条边值的最小值,即可增量d。
(6)在实流网络中增流,在残余网络中减流,Maxflow+=d,转向第(2)步。
使用最短增广路算法求解下图网络最大流。
(1)时间复杂度:从算法描述中可以看出,找到一条可增广路的时间是O(E),最多会执行O(VE)次,因为关键边的总数为O(VE)(见《趣学算法》附录I)。因此总的时间复杂度为O(VE2),其中,V为结点个数,E为边的数量。
(2)空间复杂度:使用了一个二维数组表示实流网络,因此空间复杂度为O(V2)。
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=100;
const int M=10000;
int cnt,d;
int head[N],pre[N];
bool vis[N];
struct Edge{
int v,next;
int cap,flow;
}E[M];
int n,m;
void init(){//初始化
memset(head,-1,sizeof(head));
cnt=0;
}
void add(int u,int v,int c){
E[cnt].v=v;
E[cnt].cap=c;
E[cnt].flow=0;
E[cnt].next=head[u];
head[u]=cnt++;
}
void printg(){//输出网络
cout<<endl;
cout<<"----------网络(链式前向星):----------"<<endl;
for(int i=1;i<=n;i++){
cout<<"v"<<i<<" ["<<head[i];
for(int j=head[i];~j;j=E[j].next)
cout<<"]--["<<E[j].v<<"\t"<<E[j].cap<<"\t"<<E[j].flow<<"\t"<<E[j].next;
cout<<"]"<<endl;
}
cout<<endl;
}
bool bfs(int s,int t){
memset(pre,-1,sizeof(pre));
memset(vis,0,sizeof(vis));
queue<int>q;
vis[s]=1;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];~i;i=E[i].next){
int v=E[i].v;
if(!vis[v]&&E[i].cap>E[i].flow){
vis[v]=1;
pre[v]=i;//边下标
q.push(v);
if(v==t) return 1;//找到一条可增广路
}
}
}
return 0;
}
int EK(int s,int t){
int maxflow=0;
while(bfs(s,t)){//可以增广
int d=inf,v=t;
while(v!=s){//找最小增量
int i=pre[v];
d=min(d,E[i].cap-E[i].flow);
v=E[i^1].v;
}
maxflow+=d;
cout<<"增广路径: "<<t;
v=t;
while(v!=s){//沿可增广路增流
int i=pre[v];
E[i].flow+=d;
E[i^1].flow-=d;
v=E[i^1].v;
cout<<"--"<<v;
}
cout<<"\t增流:"<<d<<endl;
printg();//输出网络
}
cout<<endl<<"网络的最大流值: "<<maxflow<<endl;
return maxflow;
}
void printflow(){//输出实流边
cout<<endl;
cout<<"----------实流边:----------"<<endl;
for(int i=1;i<=n;i++)
for(int j=head[i];~j;j=E[j].next)
if(E[j].flow>0){
cout<<"v"<<i<<"--"<<"v"<<E[j].v<<"\t"<<E[j].flow;
cout<<endl;
}
}
int main(){
int u,v,w;
cout<<"请输入结点个数n和边数m:"<<endl;
cin>>n>>m;
init();
cout<<"请输入两个结点u,v及边(u--v)的容量w:"<<endl;
for(int i=1;i<=m;i++){
cin>>u>>v>>w;
add(u,v,w);
add(v,u,0);
}
printg();//输出初始网络
EK(1,n);
printg();//输出最终网络
printflow();//输出实流边
return 0;
}
/*测试数据
6 9
1 3 10
1 2 12
2 4 8
3 5 13
3 2 2
4 6 18
4 3 5
5 6 4
5 4 6
*/
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=205;
const int M=205;
int cnt;
int head[N],pre[N];
bool vis[N];
struct Edge{
int v,next;
int cap,flow;
}E[M<<1];//双边
void init(){//初始化
memset(head,-1,sizeof(head));
cnt=0;
}
void add(int u,int v,int c){
E[cnt].v=v;
E[cnt].cap=c;
E[cnt].flow=0;
E[cnt].next=head[u];
head[u]=cnt++;
}
bool bfs(int s,int t){
memset(pre,-1,sizeof(pre));
memset(vis,0,sizeof(vis));
queue<int>q;
vis[s]=1;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];~i;i=E[i].next){
int v=E[i].v;
if(!vis[v]&&E[i].cap>E[i].flow){
vis[v]=1;
pre[v]=i;//边下标
q.push(v);
if(v==t) return 1;//找到一条可增广路
}
}
}
return 0;
}
int EK(int s,int t){
int maxflow=0;
while(bfs(s,t)){
int v=t,d=inf;
while(v!=s){//找可增量
int i=pre[v];
d=min(d,E[i].cap-E[i].flow);
v=E[i^1].v;
}
maxflow+=d;
v=t;
while(v!=s){//沿可增广路增流
int i=pre[v];
E[i].flow+=d;
E[i^1].flow-=d;
v=E[i^1].v;
}
}
return maxflow;
}
int main(){
int n,m,u,v,w;
while(~scanf("%d%d",&m,&n)){
init();
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,0);
}
printf("%d\n",EK(1,n));
}
return 0;
}
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=20;
const int M=1005;
int cnt;
int head[N],pre[N];
bool vis[N];
struct Edge{
int v,next;
int cap,flow;
}E[M<<1];//双边
void init(){//初始化
memset(head,-1,sizeof(head));
cnt=0;
}
void add(int u,int v,int c){
E[cnt].v=v;
E[cnt].cap=c;
E[cnt].flow=0;
E[cnt].next=head[u];
head[u]=cnt++;
}
bool bfs(int s,int t){
memset(pre,-1,sizeof(pre));
memset(vis,0,sizeof(vis));
queue<int>q;
vis[s]=1;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];~i;i=E[i].next){
int v=E[i].v;
if(!vis[v]&&E[i].cap>E[i].flow){
vis[v]=1;
pre[v]=i;//边下标
q.push(v);
if(v==t) return 1;//找到一条可增广路
}
}
}
return 0;
}
int EK(int s,int t){
int maxflow=0;
while(bfs(s,t)){
int v=t,d=inf;
while(v!=s){//找可增量
int i=pre[v];
d=min(d,E[i].cap-E[i].flow);
v=E[i^1].v;
}
maxflow+=d;
v=t;
while(v!=s){//沿可增广路增流
int i=pre[v];
E[i].flow+=d;
E[i^1].flow-=d;
v=E[i^1].v;
}
}
return maxflow;
}
int main(){
int T,n,m,u,v,w,cas=1;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
init();
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,0);
}
printf("Case %d: %d\n",cas++,EK(1,n));
}
return 0;
}