POJ 1273 最大流入门题 Edmonds_Karp算法
2015-07-28 23:06
543 查看
参考:http://www.cnblogs.com/zsboy/archive/2013/01/27/2878810.html
EK算法的核心:
反复寻找源点s到汇点t之间的增广路径,若有,找出增广路径上每一段[容量-流量]的最小值delta,若无,则结束。
在寻找增广路径时,可以用BFS来找,并且更新残留网络的值(涉及到反向边)。
而找到delta后,则使最大流值加上delta,更新为当前的最大流值。
Edmonds_Karp算法步骤:
在程序实现的时候,我们通常只是用一个cap数组来记录容量,而不记录流量,当流量+1的时候,我们可以通过容量-1来实现,以方便程序的实现。正向用cap[u][v],则反向用cap[v][u]表示。
9 8
1 2 1
1 3 1
2 5 1
2 6 1
3 4 1
4 5 1
5 8 1
6 7 1
7 8 1
如图:
红色代表第一次找到增广路1→2→5→8后,添加的反向弧,第二次找的增广路就是1→3→4→5→2→6→7→8。
得到数据如下:
1 2 1 0
1 3 1 0
2 5 1 0
2 6 1 0
3 4 1 0
5 8 1 0
6 7 1 0
8--5--2--1
1 3 1 0
3 4 1 0
4 5 1 0
5 2 0 -1
2 6 1 0
6 7 1 0
7 8 1 0
8--7--6--2--5--4--3--1
可以看到5 2 0 -1,程序有了反悔的机会。
EK算法的核心:
反复寻找源点s到汇点t之间的增广路径,若有,找出增广路径上每一段[容量-流量]的最小值delta,若无,则结束。
在寻找增广路径时,可以用BFS来找,并且更新残留网络的值(涉及到反向边)。
而找到delta后,则使最大流值加上delta,更新为当前的最大流值。
Edmonds_Karp算法步骤:
循环{ 初始化 寻找增广路,没有则退出 根据增广路,更新流量 }
#include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> #include<string> #include<map> #include<set> #include<algorithm> #include<vector> #include<stack> #include<queue> #include<sstream> #define OJ_PRINT 0 #define READ_FILE 0 #define ll long long using namespace std; const int NN_MAX = 210; const int MM_MAX = 250; const int INF = 0x1fffffff; const double M_DBL_MAX = 1.7976931348623158e+308; const double M_DBL_MIN = 2.2250738585072014e-308; struct Edge{ int from,to,cap,flow; Edge (int a,int b,int c,int d):from(a),to(b),cap(c),flow(d){} Edge ():from(0),to(0),cap(0),flow(0){} }; /**********************************************************/ /* s-起点,t-终点,sum-为最大流大小 */ int n,m,s,t,sum; /* flow[u][v]为<u,v>流量,cap[u][v]为<u,v>容量, **d[i]表示源点s到节点i的路径上的最小残留量,p[i]记录i的前驱用于计算根据增广路径计算更新流 */ int flow[NN_MAX][NN_MAX],cap[NN_MAX][NN_MAX],d[NN_MAX],p[NN_MAX]; Edge theEdge[MM_MAX]; /**********************************************************/ int min_2 (int x,int y) {return x<y?x:y;} int max_2 (int x,int y) {return x>y?x:y;} void Edmonds_Karp (); /**********************************************************/ int main() { //freopen ("in.txt","r",stdin); while (scanf ("%d %d",&m,&n)!=EOF) { s=1;t=n;sum=0;//初始化起点、终点 memset (flow,0,sizeof (flow)); memset (cap,0,sizeof (cap)); int a,b,c; for (int i=1;i<=m;i++){ scanf ("%d %d %d",&a,&b,&c); cap[a][b]+=c;//置容量大小 } Edmonds_Karp (); } return 0; } void Edmonds_Karp () { queue<int> qe;//用bfs找增广路 while (1) { memset (d,0,sizeof (d));//寻找最大流,初始化为0,每找一次,初始化一次 d[s]=INF; qe.push (s);//源点入队 while (!qe.empty ()) { int x=qe.front ();qe.pop (); for (int y=1;y<=n;y++)//遍历每个点 if (!d[y] && cap[x][y]-flow[x][y]>0){//如果该点没有被访问,且还有残留容量 p[y]=x;//设置父亲,用于更新流 qe.push (y); if (OJ_PRINT) printf ("%d %d:%d<-->%d\n",x,y,d[y],cap[x][y]-flow[x][y]); d[y]=min_2 (d[x],cap[x][y]-flow[x][y]);//增广路的最大值是这条路上所有边的残留容量的最小值 } } if (d[t]==0) break;//d[t]=0说明没有其他点能够到达终点,也就是没有增广路了,即得到了最大流 sum+=d[t];//累积最大流大小 for (int i=t;i!=s;i=p[i]){//从终点沿着增广路往起点走,途中更新所经过边的流 flow[p[i]][i]+=d[t];//更新正向流量 flow[i][p[i]]-=d[t];//更新反向流量 } } printf ("%d\n",sum); }
在程序实现的时候,我们通常只是用一个cap数组来记录容量,而不记录流量,当流量+1的时候,我们可以通过容量-1来实现,以方便程序的实现。正向用cap[u][v],则反向用cap[v][u]表示。
void Edmonds_Karp () { queue<int> qe; while (1) { memset (d,0,sizeof(d)); d[s]=INF; qe.push (s); while (!qe.empty ()) { int x=qe.front ();qe.pop (); for (int y=1;y<=n;y++) if (!d[y] && cap[x][y]>0){//cap[x][y]为当前残留容量 p[y]=x; qe.push (y); d[y]=min_2 (d[x],cap[x][y]); } } if (d[t]==0) break; sum+=d[t]; for (int i=t;i!=s;i=p[i]){ cap[p[i]][i]-=d[t];//正向-增广路径宽度,表示残留容量 cap[i][p[i]]+=d[t];//反向+增广路径宽度,表示已用容量 } } printf ("%d\n",sum); }上面用的都是邻接矩阵,也可以使用邻接表。注意邻接表G[i][j]保存的是结点i的第j条邻接边,在theEdge数组中的下标。注意用于回找增广路径的p[]数组使如何往回找的。
#include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> #include<string> #include<map> #include<set> #include<algorithm> #include<vector> #include<stack> #include<queue> #include<sstream> #define OJ_PRINT 1 #define READ_FILE 1 #define ll long long using namespace std; const int NN_MAX = 210; const int MM_MAX = 250; const int INF = 0x1fffffff; const double M_DBL_MAX = 1.7976931348623158e+308; const double M_DBL_MIN = 2.2250738585072014e-308; struct Edge{ int from,to,cap,flow; Edge (int a=0,int b=0,int c=0,int d=0):from(a),to(b),cap(c),flow(d){} Edge ():from(0),to(0),cap(0),flow(0){} }; /**********************************************************/ int n,m,s,t,sum;//s-起点,t-终点,sum-为最大流大小 int a[NN_MAX],p[NN_MAX]; vector<Edge> theEdge; vector<int> G[NN_MAX];//邻接表G[i][j]保存的是结点i的第j条邻接边,在theEdge数组中的下标 /**********************************************************/ int min_2 (int x,int y) {return x<y?x:y;} int max_2 (int x,int y) {return x>y?x:y;} void Edmonds_Karp (); void Init (); /**********************************************************/ int main() { if (READ_FILE) freopen ("in.txt","r",stdin); while (scanf ("%d %d",&m,&n)!=EOF) { s=1;t=n;sum=0;//初始化起点、终点 int a,b,c; Init (); for (int i=1;i<=m;i++){ scanf ("%d %d %d",&a,&b,&c); theEdge.push_back ( Edge(a,b,c) ); theEdge.push_back ( Edge(b,a) );//在正向弧后紧跟反向弧 G[a].push_back (i*2-2);//保存下标 G[b].push_back (i*2-1); } Edmonds_Karp (); } return 0; } void Edmonds_Karp () { queue<int> qee; while (1) { memset (a,0,sizeof (a)); a[s]=INF; qee.push (s); while (!qee.empty ()) { int x=qee.front ();qee.pop (); for (int i=0;i<G[x].size ();i++){ Edge e=theEdge[ G[x][i] ];//e是与结点x邻接的边 if (!a[e.to] && e.cap>e.flow){//0-(-1)也符合条件 if (OJ_PRINT) printf ("%d %d %d %d\n",e.from,e.to,e.cap,e.flow); //x是当前点的编号,e.from=x,e.to是这条边的另外一个点 p[e.to]=G[x][i];//相当于前面的p[y]=x,只是G[x][i]是所在边在theEdge中的下标 qee.push (e.to);//将下一个点入队 a[e.to]=min_2 (a[x],e.cap-e.flow);//更新残留容量为增广路中最小的残留 } } } if (a[t]==0) break; sum+=a[t]; //p[i]是所在边在theEdge的下标,theEdge[p[i]].from是增广路径中y的儿子结点x for (int i=t;i!=s;i=theEdge[p[i]].from){ if (OJ_PRINT) printf ("%d--",i); theEdge[p[i]].flow+=a[t];//正向流量+本次增广路使用的流量 theEdge[p[i]^1].flow-=a[t]; } if (OJ_PRINT) printf ("%d\n",s); } printf ("%d\n",sum); } void Init () { for (int i=0; 4000 i<n;i++) G[i].clear (); theEdge.clear(); }为了理解反向弧,我们测试如下数据:
9 8
1 2 1
1 3 1
2 5 1
2 6 1
3 4 1
4 5 1
5 8 1
6 7 1
7 8 1
如图:
红色代表第一次找到增广路1→2→5→8后,添加的反向弧,第二次找的增广路就是1→3→4→5→2→6→7→8。
得到数据如下:
1 2 1 0
1 3 1 0
2 5 1 0
2 6 1 0
3 4 1 0
5 8 1 0
6 7 1 0
8--5--2--1
1 3 1 0
3 4 1 0
4 5 1 0
5 2 0 -1
2 6 1 0
6 7 1 0
7 8 1 0
8--7--6--2--5--4--3--1
可以看到5 2 0 -1,程序有了反悔的机会。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- 关于指针的一些事情
- c++ primer 第五版 笔记前言
- share_ptr的几个注意点
- Lua中调用C++函数示例
- Lua教程(一):在C++中嵌入Lua脚本
- Lua教程(二):C++和Lua相互传递数据示例
- C++联合体转换成C#结构的实现方法
- C++编写简单的打靶游戏
- C++ 自定义控件的移植问题
- C++变位词问题分析
- C/C++数据对齐详细解析
- C++基于栈实现铁轨问题
- C++中引用的使用总结
- 使用Lua来扩展C++程序的方法
- C++中调用Lua函数实例
- Lua和C++的通信流程代码实例
- C与C++之间相互调用实例方法讲解
- C++ Custom Control控件向父窗体发送对应的消息
- C++中拷贝构造函数的应用详解