网络流三大算法邻接矩阵+邻接表POJ1273

Posted yuanweidao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络流三大算法邻接矩阵+邻接表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 }
View Code

 

邻接表(链式前向星实现)

技术图片
  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 }
View Code

 

 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 }
View Code

 

 

 

邻接表:

技术图片
不写了, 这个算法不重要 
View Code

 

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 }
View Code

 

 

 

邻接表(链式前向星实现):

技术图片
  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 }
View Code

 

  

 

以上是关于网络流三大算法邻接矩阵+邻接表POJ1273的主要内容,如果未能解决你的问题,请参考以下文章

用c语言编程 1创建图的邻接矩阵和邻接表 2验证图的深度优先、广度优先遍历算法 3验证最短路径

邻接矩阵和邻接表存储的图的基本操作及完整代码

数据结构与算法学习笔记 图

数据结构与算法学习笔记 图

POJ 1815 - Friendship - [拆点最大流求最小点割集][暴力枚举求升序割点] - [Dinic算法模板 - 邻接矩阵型]

数据结构与算法图 ( 图的存储形式 | 图的基本概念 | 图的表示方式 | 邻接矩阵 | 邻接表 | 图的创建 | 代码示例 )