zoukankan      html  css  js  c++  java
  • 网络流三大算法【邻接矩阵+邻接表】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
    ", 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
    ", 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
    ", 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
    ", max_flow(1, n));
    110     }
    111     return 0;
    112 }
    View Code

      

  • 相关阅读:
    java常用IO流集合用法模板
    java根据概率生成数字
    从浏览器直接转跳到APP具体页面---(魔窗)MagicWindow使用教程
    java开源即时通讯软件服务端openfire源码构建
    还在繁琐的敲MVP接口和实现类吗,教你一秒搞定。
    手把手带你走进MVP +Dagger2 + DataBinding+ Rxjava+Retrofit 的世界
    poj3666(DP+离散化)
    poj3616(LIS简单变式)
    hdoj2859(矩阵DP)
    poj1088(记忆化搜索入门题)
  • 原文地址:https://www.cnblogs.com/yuanweidao/p/10805011.html
Copyright © 2011-2022 走看看