网络流三大算法【邻接矩阵+邻接表】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
邻接矩阵:
//邻接矩阵 #include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#define mem(a, b) memset(a, b, sizeof(a))
const int inf = 0x3f3f3f3f;
using namespace std; int m, n;//m为边的数量, n为点的数量
int map[][];//存图
int flow[]; //记录bfs查找时的最小边,类似木桶效应
int pre[];
queue<int>Q; int bfs(int st, int ed)
{
while(!Q.empty())//队列清空
Q.pop();
for(int i = ; i <= n; i ++)//1.点的前驱, 利用前驱来更新路径上的正反向边的所剩容量 2.标记是否已经遍历过
pre[i] = -;
flow[st] = inf;//flow数组只需要每次bfs时将源点初始化为inf即可, 不需要全部初始化,因为更新时只跟上一个状态以及map边的信息有关
Q.push(st);
while(!Q.empty())
{
int index = Q.front();
Q.pop();
if(index == ed)//找到一条增广路径
break;
for(int i = ; i <= n; i ++)//遍历图
{
if(pre[i] == - && map[index][i] > && i != st)//1.没经过i点 2.边的容量大于0 3.终点不为起点,防止没经过汇点死循环
{
flow[i] = min(flow[index], map[index][i]);//找到一个可行流上最小的一条边
pre[i] = index;//记录前驱
Q.push(i);
}
}
}
if(pre[ed] == -)//汇点前驱没被更新说明没找到增广路径
return -;
else
return flow[ed];
} int max_flow(int st, int ed)
{
int inc; //每次bfs查找得到的增量
int ans = ; //记每次的增量之和为答案
while((inc = bfs(st, ed)) != -)
{
int k = ed; //从汇点往回更新
while(k != st)
{
int last = pre[k];
map[last][k] -= inc;
map[k][last] += inc;
k = last;
}
ans += inc;
}
return ans;
} int main()
{
while(scanf("%d%d", &m, &n)!=EOF)
{
mem(map, );//图的边容量初始化为0
for(int i = ; i <= m; i ++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(a == b)
continue;
map[a][b] += c;//网络流单向边,若i到j有多根管子,可看作容量叠加的一根管子
}
int ans = max_flow(, n); //源点1到汇点n的最大流
printf("%d\n", ans);
}
return ;
}
邻接表(链式前向星实现)
//链式前向星 #include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#define mem(a, b) memset(a, b, sizeof(a))
const int inf = 0x3f3f3f3f;
using namespace std; struct Edge
{
int next, to, val;
}edge[ * ];//反向边 开两倍空间 int m, n;
int head[], cnt, pos[];
int flow[];
int pre[];
queue<int>Q; void add(int a, int b, int c)
{
edge[cnt].to = b;
edge[cnt].val = c;
edge[cnt].next = head[a];
head[a] = cnt ++; edge[cnt].to = a;
edge[cnt].val = ;//反向边容量初始化为0
edge[cnt].next = head[b];
head[b] = cnt ++;
} int bfs(int st, int ed)
{
mem(pos, -);
while(!Q.empty())
Q.pop();
for(int i = ; i <= n; i ++)
pre[i] = -;
flow[st] = inf;
Q.push(st);
while(!Q.empty())
{
int a = Q.front();
Q.pop();
if(a == ed)
return flow[ed];
for(int i = head[a]; i != -; i = edge[i].next)
{
int to = edge[i].to;
if(pre[to] == - && edge[i].val > && to != st)
{
flow[to] = min(flow[a], edge[i].val);
pre[to] = a;
pos[to] = i;//储存寻找到的路径各边的位置, 用于更新val时参与 ^ 运算
Q.push(to);
}
}
}
return -;
} int max_flow(int st, int ed)
{
int inc;
int ans = ;
while((inc = bfs(st, ed)) != -)
{
int k = ed;
while(k != st)
{
edge[pos[k]].val -= inc;//巧用 ^1 运算, 0 ^ 1 = 1, 1 ^1 = 0, 2 ^ 1 = 3, 3 ^ 1 = 2, 4 ^ 1 = 5, 5 ^ 1 = 4.
// 所以这里链式前向星必须从0开始存边, 这样的话刚好正反向边与 ^ 运算一一对应,例如找到2边, 那么更新2, 3边, 找到3边,那么更新2, 3边
edge[pos[k] ^ ].val += inc;
k = pre[k];
}
ans += inc;
}
return ans;
} int main()
{
while(scanf("%d%d", &m, &n)!=EOF)
{
cnt = ;
mem(head, -);
for(int i = ; i <= m; i ++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(a == b)
continue;
add(a, b, c);//链式前向星存储的是边的位置, 不需要特别处理重边, 存了的边都会在寻找增广路中被用到
}
int ans = max_flow(, n);
printf("%d\n", ans);
}
return ;
}
2.Ford-Fulkerson
这个算法不常用, 可以用于理解后面的dinic算法, 不是很重要,在同学博客里复制过来,嘿嘿嘿
邻接矩阵
#include<iostream>
#include<string.h>
#include<queue>
using namespace std; int map[][];
int n,m;
bool vis[];//标记该点有没有用过 int dfs(int start,int ed,int cnt)
{ //cnt是查找到的增广路中流量最小的边
if(start == ed)
return cnt; //起点等于终点,即已经查到一条可行增广路
for(int i = ; i <= m; i ++)
{ //以起点start遍历与它相连的每一条边
if(map[start][i] > && !vis[i])
{ //这条边是否可行
vis[i] = true; //标记已经走过
int flow = dfs(i, ed, min(cnt, map[start][i]));//递归查找
if(flow > )
{ //回溯时更行map,这和EK的迭代更行差不多
map[start][i] -= flow;
map[i][start] += flow;
return flow;
}
}
}
return ;//没找到
}
int Max_flow(int start, int ed)
{
int ans = ;
while(true)
{
memset(vis, false, sizeof(vis));
int inc = dfs(start, ed, 0x3f3f3f3f);//查找增广路
if(inc == )
return ans;//没有增广路了
ans+=inc;
}
}
int main()
{
int start, ed, w;
while(cin >> n >> m)
{
memset(map, , sizeof(map));
for(int i = ; i < n; i ++)
{
cin >> start >> ed >> w;
if(start == ed)
continue;
map[start][ed] += w;
}
cout<<Max_flow(,m)<<endl;
}
return ;
}
邻接表:
不写了, 这个算法不重要
3.dinic
这个算法是要求掌握。这是求解网络流较高效速度比较快的方法。
算法思想:
在寻找增广路之前进行bfs对边进行分层, 例如有边, 1->2,1->3,2->4, 3->4,2->3.那么分层之后就是第一层为点1,第二层为点2, 3.第三层为点4。然后在寻找增广路径时通过层次访问, 就避免了2->3这条边的访问。提高了效率。
邻接矩阵:
#include<stdio.h>
#include<queue>
#include<string.h>
#include<algorithm>
#define mem(a, b) memset(a, b, sizeof(a))
const int inf = 0x3f3f3f3f;
using namespace std; int m, n;
int map[][];
int dep[];//点所属的层次
queue<int>Q; int bfs(int st, int ed)
{
if(st == ed)
return ;
while(!Q.empty())
Q.pop();
mem(dep, -); //层次初始化
dep[st] = ; //起点定义为第一层
Q.push(st);
while(!Q.empty())
{
int index = Q.front();
Q.pop();
for(int i = ; i <= n; i ++)
{
if(map[index][i] > && dep[i] == -)
{
dep[i] = dep[index] + ;
Q.push(i);
}
}
}
return dep[ed] != -;//返回是否能成功分层,若无法分层说明找不到增广路径了,
} int dfs(int now, int ed, int cnt)
{
if(now == ed)//跳出条件, 找到了汇点,获得一条增广路径
return cnt;
for(int i = ; i <= n; i ++)
{
if(dep[i] == dep[now] + && map[now][i] > )
{
int flow = dfs(i, ed, min(cnt, map[now][i]));
if(flow > )//这条增广路径上最小的边的flow值来更新整个路径
{
map[now][i] -= flow;
map[i][now] += flow;
return flow;
}
}
}
return -;//该种分层已经无法找到增广路径
} int max_flow(int st, int ed)
{
int ans = ;
while(bfs(st, ed))
{
while()
{
int inc = dfs(st, ed, inf);
if(inc == -)
break;
ans += inc;
}
}
return ans;
} int main()
{
while(scanf("%d%d", &m, &n)!=EOF)
{
mem(map, );
for(int i = ; i <= m; i ++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(a == b)
continue;
map[a][b] += c;
}
printf("%d\n", max_flow(, n));
}
return ;
}
邻接表(链式前向星实现):
#include<stdio.h>
#include<queue>
#include<string.h>
#include<algorithm>
#define mem(a, b) memset(a, b, sizeof(a))
const int inf = 0x3f3f3f3f;
using namespace std; int m, n;
int head[], cnt;
int dep[];
queue<int>Q; struct Edge
{
int to, next, val;
}edge[]; void add(int a, int b, int c)// ^ 运算, 从0开始存边
{
edge[cnt].to = b;
edge[cnt].val = c;
edge[cnt].next = head[a];
head[a] = cnt ++; edge[cnt].to = a;
edge[cnt].val = ;//反向边容量初始化 0
edge[cnt].next = head[b];
head[b] = cnt ++;
} int bfs(int st, int ed)
{
if(st == ed)
return ;
while(!Q.empty())
Q.pop();
mem(dep, -);//层次初始化
dep[st] = ; //第一层定义为 1
Q.push(st);
while(!Q.empty())
{
int index = Q.front();
Q.pop();
for(int i = head[index]; i != -; i = edge[i].next)
{
int to = edge[i].to;
if(edge[i].val > && dep[to] == -)
{
dep[to] = dep[index] + ;
Q.push(to);
}
}
}
return dep[ed] != -;
} int dfs(int now, int ed, int cnt)
{
if(now == ed)
return cnt;
for(int i = head[now]; i != -; i = edge[i].next)
{
int to = edge[i].to;
if(dep[to] == dep[now] + && edge[i].val > )
{
int flow = dfs(to, ed, min(cnt, edge[i].val));
if(flow > )
{
edge[i].val -= flow;
edge[i ^ ].val += flow;
return flow;
}
}
}
return -;
} int max_flow(int st, int ed)
{
int ans = ;
while(bfs(st, ed))
{
while()
{
int inc = dfs(st, ed, inf);
if(inc == -)
break;
ans += inc;
}
}
return ans;
} int main()
{
while(scanf("%d%d", &m, &n)!=EOF)
{
cnt = ;
mem(head, -);
for(int i = ; i <= m; i ++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(a == b)
continue;
add(a, b, c);
}
printf("%d\n", max_flow(, n));
}
return ;
}
网络流三大算法【邻接矩阵+邻接表】POJ1273的更多相关文章
- 数据结构学习笔记05图 (邻接矩阵 邻接表-->BFS DFS、最短路径)
数据结构之图 图(Graph) 包含 一组顶点:通常用V (Vertex) 表示顶点集合 一组边:通常用E (Edge) 表示边的集合 边是顶点对:(v, w) ∈E ,其中v, w ∈ V 有向边& ...
- hdu 1874 畅通工程(spfa 邻接矩阵 邻接表)
题目链接 畅通工程,可以用dijkstra算法实现. 听说spfa很好用,来水一发 邻接矩阵实现: #include <stdio.h> #include <algorithm> ...
- 第6章 图的学习总结(邻接矩阵&邻接表)
我觉得图这一章的学习内容更有难度,其实图可以说是树结构更为普通的表现形式,它的每个元素都可以与多个元素之间相关联,所以结构比树更复杂,然而越复杂的数据结构在现实中用途就越大了,功能与用途密切联系,所以 ...
- POJ 3013 SPFA算法,邻接表的使用
Big Christmas Tree Time Limit: 3000MS Memory Limit: 131072K Total Submissions: 19029 Accepted: 4 ...
- 图的全部实现(邻接矩阵 邻接表 BFS DFS 最小生成树 最短路径等)
1 /** 2 * C: Dijkstra算法获取最短路径(邻接矩阵) 3 * 6 */ 7 8 #include <stdio.h> 9 #include <stdlib.h> ...
- HDU 1874 畅通工程续(最短路/spfa Dijkstra 邻接矩阵+邻接表)
题目链接: 传送门 畅通工程续 Time Limit: 1000MS Memory Limit: 65536K Description 某省自从实行了很多年的畅通工程计划后,终于修建了很多路. ...
- <图论入门>邻接矩阵+邻接表
非本人允许请勿转载. 趁热打铁,学会了邻接表把这个总结一下,以及感谢大佬uncle-lu!!!(奶一波)祝早日进队! 首先,图论入门就得是非常基础的东西,先考虑怎么把这个图读进去. 给定一个无向图,如 ...
- 最短路径SPFA算法(邻接表存法)
queue <int> Q; void SPFA (int s) { int i, v; for(int i=0; i<=n; i++) dist[i]=INF; //初始化每点i到 ...
- c语言实现迪杰斯特拉算法(邻接表)
储存结构,结构体的定义:(权值w用于表示两点间路径的花费) typedef int Status; typedef struct ENode//图的邻接表定义 { int adjVex;//任意顶点u ...
随机推荐
- intellij idea gradle 导入 spring 问题记录
环境: windows 7 oracle jdk 1.8 intellij idea 2019.3.1 spring-framework 5.1.22.RELEASE 步骤: 1: 下载解压sprin ...
- 【ArcCatalog】
1.添加文件夹链接文件夹链接的添加删除操作 2.新建/删除数据库(1)新建个人地理数据库(.mdb)(2)新建文本地理数据库(.gdb) 3.新建/删除要素数据集(1)要素数据集属性: 常规--XY坐 ...
- nginx大概工作机制
1.master和worker nginx启动后,会有2种进程:worker和master;worker可能有多个:
- 有关 C# WebAPI知识
1.[懒得安分博客总结的很全面] 2.关于基础类型作入参数的问题 参照此博客[ASP.NET WebAPI String 传值问题] 3.代码说明 using System; usi ...
- ARM编辑、编译工具
手动编译 编译器问题,肯定是GNU的大名鼎鼎的GCC了,与此相关的什么连接器,汇编器也都包含在内了. 针对arm的GCC,当然就是arm-linux-gcc了,我所用的版本就是友善之臂光盘自带arm- ...
- which/whereis
which 查找二进制命令,按环境变量PATH路径查找 whereis 查找二进制命令,按环境变量PATH路径查找 查询命令的安装路径,配置文件路径
- git上传超过100m大文件
1.git出错如下错误时 执行如下可解决错误: git rm --cache '大文件路径' git commit --amend -CHEAD git push 2.当必须上传大文件时.需借助git ...
- (转载)IOCP 浅析
转自:http://www.ibm.com/developerworks/cn/java/j-lo-iocp/#author 郭 仁祥, 软件工程师, IBM 简介: 传统的 Server/Cli ...
- 使用 in_memory 工作空间的注意事项
来自:https://pro.arcgis.com/zh-cn/pro-app/tool-reference/appendices/using-the-in-memory-output-workspa ...
- sftp winscp
https://stackoverflow.com/questions/16150152/secure-ftp-using-windows-batch-script First, make sure ...