网络流三大算法【邻接矩阵+邻接表】POJ1273
网络流的基本概念跟算法原理我是在以下两篇博客里看懂的,写的非常好。
http://www.cnblogs.com/ZJUT-jiangnan/p/3632525.html
http://www.cnblogs.com/zsboy/archive/2013/01/27/2878810.html
网络流有四种算法, 包括 Edmond-Karp(简称EK), Ford-Fulkerson(简称FF), dinic算法以及SAP算法。
下面我会写出前三种算法的矩阵跟邻接表的形式, 对于第四种以后有必要再补充上, 其中dinic算法是比较高效的算法, 要重点掌握dinc,其他两种我是当做辅助理解网络流的,以后做题还是得练dinic
领接矩阵:适用于稠密图,原因:矩阵大小是 n * n的,若边m << n * n,则矩阵中有很多空位置,造成空间浪费, 所以只有稠密图才适合矩阵写法
邻接表:适用于稀疏图,原因:附加链域,稠密图不适合
附上例题链接:http://poj.org/problem?id=1273
1.Edmond-Karp
邻接矩阵:
1 //邻接矩阵 2 3 #include<stdio.h> 4 #include<string.h> 5 #include<algorithm> 6 #include<queue> 7 #define mem(a, b) memset(a, b, sizeof(a)) 8 const int inf = 0x3f3f3f3f; 9 using namespace std; 10 11 int m, n;//m为边的数量, n为点的数量 12 int map[210][210];//存图 13 int flow[210]; //记录bfs查找时的最小边,类似木桶效应 14 int pre[210]; 15 queue<int>Q; 16 17 int bfs(int st, int ed) 18 { 19 while(!Q.empty())//队列清空 20 Q.pop(); 21 for(int i = 1; i <= n; i ++)//1.点的前驱, 利用前驱来更新路径上的正反向边的所剩容量 2.标记是否已经遍历过 22 pre[i] = -1; 23 flow[st] = inf;//flow数组只需要每次bfs时将源点初始化为inf即可, 不需要全部初始化,因为更新时只跟上一个状态以及map边的信息有关 24 Q.push(st); 25 while(!Q.empty()) 26 { 27 int index = Q.front(); 28 Q.pop(); 29 if(index == ed)//找到一条增广路径 30 break; 31 for(int i = 1; i <= n; i ++)//遍历图 32 { 33 if(pre[i] == -1 && map[index][i] > 0 && i != st)//1.没经过i点 2.边的容量大于0 3.终点不为起点,防止没经过汇点死循环 34 { 35 flow[i] = min(flow[index], map[index][i]);//找到一个可行流上最小的一条边 36 pre[i] = index;//记录前驱 37 Q.push(i); 38 } 39 } 40 } 41 if(pre[ed] == -1)//汇点前驱没被更新说明没找到增广路径 42 return -1; 43 else 44 return flow[ed]; 45 } 46 47 int max_flow(int st, int ed) 48 { 49 int inc; //每次bfs查找得到的增量 50 int ans = 0; //记每次的增量之和为答案 51 while((inc = bfs(st, ed)) != -1) 52 { 53 int k = ed; //从汇点往回更新 54 while(k != st) 55 { 56 int last = pre[k]; 57 map[last][k] -= inc; 58 map[k][last] += inc; 59 k = last; 60 } 61 ans += inc; 62 } 63 return ans; 64 } 65 66 int main() 67 { 68 while(scanf("%d%d", &m, &n)!=EOF) 69 { 70 mem(map, 0);//图的边容量初始化为0 71 for(int i = 1; i <= m; i ++) 72 { 73 int a, b, c; 74 scanf("%d%d%d", &a, &b, &c); 75 if(a == b) 76 continue; 77 map[a][b] += c;//网络流单向边,若i到j有多根管子,可看作容量叠加的一根管子 78 } 79 int ans = max_flow(1, n); //源点1到汇点n的最大流 80 printf("%d\n", ans); 81 } 82 return 0; 83 }
邻接表(链式前向星实现)
1 //链式前向星 2 3 #include<stdio.h> 4 #include<string.h> 5 #include<algorithm> 6 #include<queue> 7 #define mem(a, b) memset(a, b, sizeof(a)) 8 const int inf = 0x3f3f3f3f; 9 using namespace std; 10 11 struct Edge 12 { 13 int next, to, val; 14 }edge[210 * 2];//反向边 开两倍空间 15 16 int m, n; 17 int head[210], cnt, pos[210]; 18 int flow[210]; 19 int pre[210]; 20 queue<int>Q; 21 22 void add(int a, int b, int c) 23 { 24 edge[cnt].to = b; 25 edge[cnt].val = c; 26 edge[cnt].next = head[a]; 27 head[a] = cnt ++; 28 29 edge[cnt].to = a; 30 edge[cnt].val = 0;//反向边容量初始化为0 31 edge[cnt].next = head[b]; 32 head[b] = cnt ++; 33 } 34 35 int bfs(int st, int ed) 36 { 37 mem(pos, -1); 38 while(!Q.empty()) 39 Q.pop(); 40 for(int i = 1; i <= n; i ++) 41 pre[i] = -1; 42 flow[st] = inf; 43 Q.push(st); 44 while(!Q.empty()) 45 { 46 int a = Q.front(); 47 Q.pop(); 48 if(a == ed) 49 return flow[ed]; 50 for(int i = head[a]; i != -1; i = edge[i].next) 51 { 52 int to = edge[i].to; 53 if(pre[to] == -1 && edge[i].val > 0 && to != st) 54 { 55 flow[to] = min(flow[a], edge[i].val); 56 pre[to] = a; 57 pos[to] = i;//储存寻找到的路径各边的位置, 用于更新val时参与 ^ 运算 58 Q.push(to); 59 } 60 } 61 } 62 return -1; 63 } 64 65 int max_flow(int st, int ed) 66 { 67 int inc; 68 int ans = 0; 69 while((inc = bfs(st, ed)) != -1) 70 { 71 int k = ed; 72 while(k != st) 73 { 74 edge[pos[k]].val -= inc;//巧用 ^1 运算, 0 ^ 1 = 1, 1 ^1 = 0, 2 ^ 1 = 3, 3 ^ 1 = 2, 4 ^ 1 = 5, 5 ^ 1 = 4. 75 // 所以这里链式前向星必须从0开始存边, 这样的话刚好正反向边与 ^ 运算一一对应,例如找到2边, 那么更新2, 3边, 找到3边,那么更新2, 3边 76 edge[pos[k] ^ 1].val += inc; 77 k = pre[k]; 78 } 79 ans += inc; 80 } 81 return ans; 82 } 83 84 int main() 85 { 86 while(scanf("%d%d", &m, &n)!=EOF) 87 { 88 cnt = 0; 89 mem(head, -1); 90 for(int i = 1; i <= m; i ++) 91 { 92 int a, b, c; 93 scanf("%d%d%d", &a, &b, &c); 94 if(a == b) 95 continue; 96 add(a, b, c);//链式前向星存储的是边的位置, 不需要特别处理重边, 存了的边都会在寻找增广路中被用到 97 } 98 int ans = max_flow(1, n); 99 printf("%d\n", ans); 100 } 101 return 0; 102 }
2.Ford-Fulkerson
这个算法不常用, 可以用于理解后面的dinic算法, 不是很重要,在同学博客里复制过来,嘿嘿嘿
邻接矩阵
1 #include<iostream> 2 #include<string.h> 3 #include<queue> 4 using namespace std; 5 6 int map[300][300]; 7 int n,m; 8 bool vis[300];//标记该点有没有用过 9 10 int dfs(int start,int ed,int cnt) 11 { //cnt是查找到的增广路中流量最小的边 12 if(start == ed) 13 return cnt; //起点等于终点,即已经查到一条可行增广路 14 for(int i = 1; i <= m; i ++) 15 { //以起点start遍历与它相连的每一条边 16 if(map[start][i] > 0 && !vis[i]) 17 { //这条边是否可行 18 vis[i] = true; //标记已经走过 19 int flow = dfs(i, ed, min(cnt, map[start][i]));//递归查找 20 if(flow > 0) 21 { //回溯时更行map,这和EK的迭代更行差不多 22 map[start][i] -= flow; 23 map[i][start] += flow; 24 return flow; 25 } 26 } 27 } 28 return 0;//没找到 29 } 30 int Max_flow(int start, int ed) 31 { 32 int ans = 0; 33 while(true) 34 { 35 memset(vis, false, sizeof(vis)); 36 int inc = dfs(start, ed, 0x3f3f3f3f);//查找增广路 37 if(inc == 0) 38 return ans;//没有增广路了 39 ans+=inc; 40 } 41 } 42 int main() 43 { 44 int start, ed, w; 45 while(cin >> n >> m) 46 { 47 memset(map, 0, sizeof(map)); 48 for(int i = 0; i < n; i ++) 49 { 50 cin >> start >> ed >> w; 51 if(start == ed) 52 continue; 53 map[start][ed] += w; 54 } 55 cout<<Max_flow(1,m)<<endl; 56 } 57 return 0; 58 }
邻接表:
不写了, 这个算法不重要
3.dinic
这个算法是要求掌握。这是求解网络流较高效速度比较快的方法。
算法思想:
在寻找增广路之前进行bfs对边进行分层, 例如有边, 1->2,1->3,2->4, 3->4,2->3.那么分层之后就是第一层为点1,第二层为点2, 3.第三层为点4。然后在寻找增广路径时通过层次访问, 就避免了2->3这条边的访问。提高了效率。
邻接矩阵:
1 #include<stdio.h> 2 #include<queue> 3 #include<string.h> 4 #include<algorithm> 5 #define mem(a, b) memset(a, b, sizeof(a)) 6 const int inf = 0x3f3f3f3f; 7 using namespace std; 8 9 int m, n; 10 int map[210][210]; 11 int dep[210];//点所属的层次 12 queue<int>Q; 13 14 int bfs(int st, int ed) 15 { 16 if(st == ed) 17 return 0; 18 while(!Q.empty()) 19 Q.pop(); 20 mem(dep, -1); //层次初始化 21 dep[st] = 1; //起点定义为第一层 22 Q.push(st); 23 while(!Q.empty()) 24 { 25 int index = Q.front(); 26 Q.pop(); 27 for(int i = 1; i <= n; i ++) 28 { 29 if(map[index][i] > 0 && dep[i] == -1) 30 { 31 dep[i] = dep[index] + 1; 32 Q.push(i); 33 } 34 } 35 } 36 return dep[ed] != -1;//返回是否能成功分层,若无法分层说明找不到增广路径了, 37 } 38 39 int dfs(int now, int ed, int cnt) 40 { 41 if(now == ed)//跳出条件, 找到了汇点,获得一条增广路径 42 return cnt; 43 for(int i = 1; i <= n; i ++) 44 { 45 if(dep[i] == dep[now] + 1 && map[now][i] > 0) 46 { 47 int flow = dfs(i, ed, min(cnt, map[now][i])); 48 if(flow > 0)//这条增广路径上最小的边的flow值来更新整个路径 49 { 50 map[now][i] -= flow; 51 map[i][now] += flow; 52 return flow; 53 } 54 } 55 } 56 return -1;//该种分层已经无法找到增广路径 57 } 58 59 int max_flow(int st, int ed) 60 { 61 int ans = 0; 62 while(bfs(st, ed)) 63 { 64 while(1) 65 { 66 int inc = dfs(st, ed, inf); 67 if(inc == -1) 68 break; 69 ans += inc; 70 } 71 } 72 return ans; 73 } 74 75 int main() 76 { 77 while(scanf("%d%d", &m, &n)!=EOF) 78 { 79 mem(map, 0); 80 for(int i = 1; i <= m; i ++) 81 { 82 int a, b, c; 83 scanf("%d%d%d", &a, &b, &c); 84 if(a == b) 85 continue; 86 map[a][b] += c; 87 } 88 printf("%d\n", max_flow(1, n)); 89 } 90 return 0; 91 }
邻接表(链式前向星实现):
1 #include<stdio.h> 2 #include<queue> 3 #include<string.h> 4 #include<algorithm> 5 #define mem(a, b) memset(a, b, sizeof(a)) 6 const int inf = 0x3f3f3f3f; 7 using namespace std; 8 9 int m, n; 10 int head[210], cnt; 11 int dep[210]; 12 queue<int>Q; 13 14 struct Edge 15 { 16 int to, next, val; 17 }edge[500]; 18 19 void add(int a, int b, int c)// ^ 运算, 从0开始存边 20 { 21 edge[cnt].to = b; 22 edge[cnt].val = c; 23 edge[cnt].next = head[a]; 24 head[a] = cnt ++; 25 26 edge[cnt].to = a; 27 edge[cnt].val = 0;//反向边容量初始化 0 28 edge[cnt].next = head[b]; 29 head[b] = cnt ++; 30 } 31 32 int bfs(int st, int ed) 33 { 34 if(st == ed) 35 return 0; 36 while(!Q.empty()) 37 Q.pop(); 38 mem(dep, -1);//层次初始化 39 dep[st] = 1; //第一层定义为 1 40 Q.push(st); 41 while(!Q.empty()) 42 { 43 int index = Q.front(); 44 Q.pop(); 45 for(int i = head[index]; i != -1; i = edge[i].next) 46 { 47 int to = edge[i].to; 48 if(edge[i].val > 0 && dep[to] == -1) 49 { 50 dep[to] = dep[index] + 1; 51 Q.push(to); 52 } 53 } 54 } 55 return dep[ed] != -1; 56 } 57 58 int dfs(int now, int ed, int cnt) 59 { 60 if(now == ed) 61 return cnt; 62 for(int i = head[now]; i != -1; i = edge[i].next) 63 { 64 int to = edge[i].to; 65 if(dep[to] == dep[now] + 1 && edge[i].val > 0) 66 { 67 int flow = dfs(to, ed, min(cnt, edge[i].val)); 68 if(flow > 0) 69 { 70 edge[i].val -= flow; 71 edge[i ^ 1].val += flow; 72 return flow; 73 } 74 } 75 } 76 return -1; 77 } 78 79 int max_flow(int st, int ed) 80 { 81 int ans = 0; 82 while(bfs(st, ed)) 83 { 84 while(1) 85 { 86 int inc = dfs(st, ed, inf); 87 if(inc == -1) 88 break; 89 ans += inc; 90 } 91 } 92 return ans; 93 } 94 95 int main() 96 { 97 while(scanf("%d%d", &m, &n)!=EOF) 98 { 99 cnt = 0; 100 mem(head, -1); 101 for(int i = 1; i <= m; i ++) 102 { 103 int a, b, c; 104 scanf("%d%d%d", &a, &b, &c); 105 if(a == b) 106 continue; 107 add(a, b, c); 108 } 109 printf("%d\n", max_flow(1, n)); 110 } 111 return 0; 112 }