网络最大流各算法总结
网络最大流的算法共有两大类5种算法 总体如下表:n为顶点数,m为弧的数目,U代表各条弧的最大容量
算法名称 | 复杂度 | 算法概要 |
---|---|---|
一般增广路算法 | \(O(nmU)\) | 采取标号法每次在容量网络中寻找一条增广路进行增广(或在残留网络中每次任意寻找一条增广路进行增广),直至不存在增广路为止。 |
最短增广路算法 | \(O(nm^2)\) | 每个阶段:在层次网络中,不断用BFS算法进行增广直至不存在增广路为止。如果汇点不在层次网络中,算法结束。 |
连续最短增广路算法(Dinic) | \(O(n^2m)\) | 在最短增广路算法的基础上改造:在每个阶段,用一个dfs过程实现多次增广。如果汇点不在层次网络中,则算法结束。 |
一般预流推进算法 | \(O (n^2m)\) | 维护一个预流,不断地对活跃顶点执行推进(Push)操作或重标号(Relabel)操作来调整这个预流,直到不能操作。 |
最高标号预流推进算法 | \(O( n^2\sqrt m)\) | 每次检查具有最高标号的活跃结点 |
一般增广路算法(Ford-Fulkerson):
1) 标号过程 每个顶点的标号有两个量:流入该顶点的流量alpha[u] 指明标号从哪个顶点得到pre[u]
2)调整过程 在每次得到最后V的标号后 根据 a = alpha[n-1] 来对这条增广路进行优化
\(f(u,v) = f(u,v) + a\ when<u,v>\in P+\)
\(f(u,v) = f(u,v)+a\ when <u,v>\in P-\)
$ f(u,v) = f(u,v) when <u,v>\notin P$
通过bfs来进行多次增广路的优化 代码如下:
#include<bits/stdc++.h>
using namespace std;
#define MAXN 1000
#define INF 1000000//根据题目条件改变
struct ArcType {
int c,f;//容量,流量
};
ArcType Edge[MAXN][MAXN];
int n,m;
int flag[MAXN];//顶点状态:-1未标号 0已标号未检查 1已标号以检查
int pre[MAXN];//标号的第一个分量:指明从哪个点得到,以便找出可改进量和增广路径
int alpha[MAXN];//标号的第二个分量:可改进量a
queue<int > q;
int v;
void ford() {
for(;;) {//标号直至不存在可改进路
memset(flag,0xff,sizeof(flag));//均初始化为-1
memset(pre,0xff,sizeof(pre));
memset(alpha,0xff,sizeof(alpha));
flag[0] = pre[0] = 0; alpha[0] = INF;//对源点标号
while(!q.empty()) q.pop();//清空队列
q.push(0);
while(!q.empty() && flag[n-1] == -1) {
v = q.front();q.pop();
for(int i = 0;i < n;i++) {
if(flag[i] == -1) {//正向且未饱和
if(Edge[v][i].c < INF && Edge[v][i].f < Edge[v][i].c) {
flag[i] = 0;pre[i] = v;
alpha[i] = min(alpha[v],Edge[v][i].c - Edge[v][i].f);
q.push(i);
}
else if(Edge[i][v].c < INF && Edge[i][v].f > 0) {//反向且非0
flag[i] = 0;pre[i] = -v;
alpha[i] = min(Edge[i][v].f,alpha[v]);
q.push(i);
}
}
}
flag[v] = 1;
}
if(flag[n-1] == -1 || alpha[n-1] == 0) break;//当汇点无需调整,退出循环
int k1 = n-1,k2 = abs(pre[k1]);
int a = alpha[n-1];
while(1) {//沿着路径改进
if(Edge[k2][k1].f < INF) Edge[k2][k1].f += a;
else Edge[k1][k2].f -= a;
if(k2 == 0) break;
k1 = k2; k2 = abs(pre[k2]);
}
}
int maxFlow = 0;
for(int i = 0;i < n;i++) {
for(int j = 0;j < n;j++) {
if(i == 0 && Edge[i][j].f < INF) maxFlow+=Edge[i][j].f;//求出源点的流出量
if(Edge[i][j].f < INF) printf("%d->%d:%d\n",i,j,Edge[i][j].f);
}
}
printf("maxFlow:%d\n",maxFlow);
}
int main() {
int u,v,c,f;
scanf("%d%d",&n,&m);
for(int i = 0;i < n;i++) {
for(int j = 0;j < n;j++) Edge[i][j].c = Edge[i][j].f = INF;
}
for(int i = 0;i < m;i++) {
scanf("%d%d%d%d",&u,&v,&c,&f);
Edge[u][v].c = c;Edge[u][v].f = f;
}
ford();
return 0;
}
复杂度简要分析:
很明显,如果容量网络中各弧的容量和初始流量均为正整数,则Ford-Fulkerson算法每增广一次,流量至少会增加一个单位,因此Ford-Fulkerson算法肯定能在有限的步骤内使得网络流达到最大。类似的理由可以说明如果弧上的容量为有理数时,也可在有限的步骤内使得网络流达到最大。但是如果弧上的容量可以是无理数,则该算法不一定在有限步内终止。
由于割$ { Vs} , V-{ Vs}$ 中前向弧的条数最多为n条,因此最大流流量|F|的上界为nU(U表示网络中各个弧的最大容量)。此外,由于每次增广最多需要对所有弧检查一遍,所以Ford-Fulkerson算法的时间复杂度为$ O(mnU)$ 。
例题:Poj1149
最短增广路算法:
最短增广路的思想:
(1)初始化容量网络和网络流。
(2)构造残留网络和层次网络,若汇点不在层次网络中,则算法结束。
(3)在层次网络中不断用BFS增广,直到层次网络中没有增广路为止;每次增广完毕,在层次网络中要去掉因改进流量而导致饱和的弧。
(4)转步骤(2)
连续最短增广路算法(Dinic算法):
Dinic算法思路:
Dinic算法的思想也是分阶段地在层次网络中增广。它与最短路算法不同之处是:最短路增广每个阶段执行完一次BFS增广后,要重新启动BFS从源点Vs开始寻找另一条增广路;而在Dinic算法中,只需一次dfs过程就可以实现多次增广,这是Dinic算法的巧妙之处。Dinic算法的具体步骤如下:
(1)初始化容量网络和网络流
(2)构造残留网络和层次网络,若汇点不在层次网络中,则算法结束。
(3)在层次网络中用一次dfs过程进行增广,dfs过程执行完毕,则该阶段的增广也执行完毕。
(4)转步骤(2)
在Dinic算法中,只有第(3)步和最短增广路算法不同。效率得到非常大的提高。
一般预流推进算法
1.增广路算法的缺点:
增广路算法是找到增广路后,立即沿增广路对网络流进行增广。每一次增广可能需要对最多n-1条弧进行操作,因此,每次增广的复杂度为\(O(n)\) ,在有些情况下,这个操作的代价是很高的。
2.距离标号
对于一个残留网络G',如何确定其精确的距离标号呢?可以从汇点Vt开始,对弧进行广度优先搜索,这一过程的复杂度为\(O(m)\)
预流:设\(f = \{ f(u,v)\}\) 是容量网络的一个网络流,如果G的每一条边弧都满足: \(0\leq f(u,v) \leq c(u,v)\)
另外,除源点汇点以外每个顶点u的盈余e(u)都满足:\(e(u) \geq 0\) 则称该网络流是G的预流。
预流推进通过不断的对活跃节点的改进来达到最大流