网络流三大算法【邻接矩阵+邻接表】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 ...
随机推荐
- Can't install '*' from pristine store, because no checksum is recorded for this file
svn同步时,提示clean up,但clean up 时提示: Error:Error performing cleanup for 'E:\project\projectProjectIDEA\b ...
- 洛谷P4698 [CEOI2011]Hotel [贪心,二分,并查集]
题目传送门 Hotel 题目描述 你经营着一家旅馆,这家旅馆有 n 个房间,每个房间有维护费用和容量.其中第 i 个房间的维护费用为 ci,容量为 pi 人. 现在有 m 个订单,每个订单有两个参 ...
- python 处理 json 四个函数dumps、loads、dump、load的区别
1 .json.dumps() 函数是将一个 Python 数据类型列表(可以理解为字典)进行json格式的编码(转换成字符串,用于传播)eg, dict = {"age": &q ...
- CodeForces 535C Tavas and Karafs —— 二分
题意:给出一个无限长度的等差数列(递增),每次可以让从l开始的m个减少1,如果某个位置已经是0了,那么可以顺延到下一位减少1,这样的操作最多t次,问t次操作以后从l开始的最长0序列的最大右边界r是多少 ...
- Error creating bean with name 'persistenceExceptionTranslationPostProcessor' defined in class path resource [org/springframework/boot/autoconfigure/dao/PersistenceExceptionTranslationAutoConfiguration
dubbo 包和SpringBoot 冲突,注释就可以正常启动
- 最初学习mysql的一些操作留存
一:数据库的初始话操作 mysql -u root -p //数据库的登陆 show databases: //展现数据哭中存储的所有文件 use 数据库名: //进入当前要 ...
- Linux设备驱动程序 之 poll和select
使用非阻塞IO的应用程序经常使用select,poll,epoll系统调用:它们的功能本质上是一样的:都允许进程决定是否可以对一个或者多个打开的文件做非阻塞的读取或者写入:这些电泳也会阻塞进程,直到给 ...
- 第11组 Alpha冲刺(1/6)
队名 不知道叫什么团队 组长博客 组长博客 作业博客 https://edu.cnblogs.com/campus/fzu/SE_FZU_1917_K/homework/9938 项目情况 燃尽图 陈 ...
- vsftpd 配置用户及根目录及其参数详解
vsftpd 常用功能参数配置及参数详解 Table of Contents 1. 配置超级服务 2. 配置匿名用户 3. 配置本地用户登录 4. 配置虚拟用户登录 5. 使用SSL登入 6. 日志文 ...
- JAVA反射机制,总结下
1.总体概述 Java反射机制指的是在Java程序运行状态中,对于任何一个类,都可以获得这个类的所有属性和方法:对于给定的一个对象,都能够调用它的任意一个属性和方法.这种动态获取类的内容以及动态调用对 ...