网络最大流算法

网络最大流是指在一个网络流图中可以从源点流到汇点的最大的流量。求解网络最大流的常用算法可以分为增广路径算法和预推进算法。其中,预推进算法的理论复杂度优于增广路径算法,但是编码复杂度过高,且效率优势在很多时候并不是很明显,因此,经常使用的算法为增广路径算法。 
    增广路径算法主要有Fold-Fulkerson算法,Edmonds-Karp算法,Dinic算法,ISAP算法。其中,Fold-Fulkerson 是最基本的增广路径思想,不能算作严格的算法实现。 
增广路径 
    增广路径算法的思想是每次从源点出发找到一条到达汇点的可行路径,那么从源点到汇点的网络流至少可以增加w(w为这条路径上的边的最小容量)。此时,将最大流增加w,这条路径称为增广路径,同时从源到汇沿着增广路径将经过的每条正向边(从源指向汇)的流量都减去w,并将每条边的反向边的流量加上w。这个操作就为增广操作。 
    不断的进行增广操作,直到无法从源到达汇停止。那么,此时得到最大流的流量。同时,可以得到在获得最大流的时候,每条边上的流量分布(只需要将原图中每条边的容量减去最后的残余网络中每条边对应的流量即可)。 
残余网络 
    在增广路径的过程中每次进行增广操作之后,得到的新图称为旧图的残余网络。

1. Fold-Fulkerson算法

Fold-Fulkerson算法就是朴素的增广路径思想。 
求最大流的过程,就是不断找到一条从源到汇的路径,然后构造残余网络,再在残余网络的基础上寻找新的路径,使总流量增加,然后形成新的残余网络,在寻找新路径.... 直到某个残余网络上找不到从源到汇的路径为止。 
    每用DFS执行一次找路径,增广的操作,都会使得最大流增加,假设最大流为C,那么时间复杂度可以达到 C*(m+n), m为边的数目,n为顶点的数目。

2. Edmonds-Karp算法

Edmonds-Karp算法是在Fold-Fulkerson思想上进行改进: 
每次寻找增广路径的时候,总是寻找一条从源到汇经过节点数目最少的路径,即最短路径。这是一种“最短增广路径” Shortest Augmenting Path(SAP)的算法。 
    在实现的时候,每次利用BFS搜索,找到一条从源到汇的最短路径,然后进行增广操作;再进行BFS....直到无法找到从源到汇的路径为止。 
    时间复杂度可以达到 n*m*m, n为顶点的数目,m为边的数目。

3. Dinic算法

Edmonds-Karp算法每找到一条增广路径进行增广操作之后都再次回到原点重新进行BFS,这样效率较低。Dinic算法是在找到一条增广路径,增广操作完之后,并不回溯到源点,而是回到距离源点最近的边上流量为0的边的起点。因为每次增广操作,都会使得增广路径上的某些边的流量变为0,这样这条增广路径上的某些边就无法再走,需要回溯,但是回溯并不需要回溯到源点,只需要回溯到一点A,使得从源点到点A的路径上的流量不为0,且点A尽可能靠近汇点(为了遍历所有情况)即可。 
    具体的算法流程是: 
1. 先用BFS对网络进行分层,分层是指按照距离源点的最近距离大小对各个点进行标号。 
2. 然后利用DFS从前一层向后一层反复 每次选择增广路径的时候,从点u总是选择满足关系 dist[v] = dist[u] + 1的点v,这样u->v的路径肯定属于某条最短路。 
3. 找到一条增广路径之后进行增广操作 
4.路增广操作之后进行回溯,将点u回溯到点A,使得从源点到点A的路径上流量不为0,且点A尽可能靠近汇点径5. 从点u开始继续选择可行点v(满足 dist[v] = dist[u] + 1),直到汇点,这样就又找到一条增广路径....
6. 直到点u回溯到源点,再回到1继续操作,直到在分层操作时,无法用BFS找到从源到汇的路径。

4. ISAP算法

ISAP算法为优化的最短增广路径算法(Improved Shortest Augmenting Path)。相比Dinic,ISAP算法不需要在回溯到源点之后再次进行BFS分层操作,而是在DFS以及回溯的过程中就进行了节点的重标号(即分层操作);以及ISAP算法进行gap优化大大提升效率。 
    具体流程为: 
1. 定义dist[v] 为点v到达汇点的距离(即经过几个点到达汇点),定义gap[d]在当前残余网络中到达汇点(经过路径上流量不能为0)距离为d的点的个数。 
2. 从汇点到源点进行BFS,标记下每个节点到达汇点的最短距离,即和Dinic算法相反的分层操作。 
3. 当前点u从源点出发,用DFS找到一条到达汇点的路径.... 
4. 若点u为汇点,则找到了一条增广路径,进行增广操作;若点u可以向前走到v,且u-->v为一条可行边(dist[u] = dist[v]+1,且边u-->v流量不为0),则u走到v;若u无法向前推进到任何点,则对u进行重标号,然后回溯u到原来的增广路径中上一个点 pre[u]. 
5. 在重标号和回溯之前,可以进行gap优化。gap[d]在当前残余网络中到达汇点(经过路径上流量不能为0)距离为d的点的个数。 若从u无法找到一条可行边,则表明可以经过 dist[u] 条边到达汇点的点数目少了一个,即 gap[dist[u]] --。若此时 gap[dist[u]] = 0,则说明当前残余网络中,没有任何一个点可以经过dist[u]条边到达汇点,源点到汇点的距离肯定大于等于dist[u],若源点能够到达汇点,那么要求源点到汇点的路径中肯定有到达汇点距离为dist[u]的点,所以,无法从源点到达汇点,此时,可以直接返回结果。 
6. 重标号,是将点u重新分层,重新设置点u经过不为0的边可达汇点的最短距离。具体是 dist[u] = min{dist[v]|u连接到v,且u-->v边流量不为0} + 1. 若从u出发的边流量均为0,则无法找到下一个点,则直接将dist[u]置为n(n为节点个数),这样就说明u点不可达。

ISAP算法的实现(c++)

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<vector>
#include<queue>
#include<map>
#include<algorithm>
using namespace std;
#define INFINITE 1 << 28
#define MAX_NODE 205
#define MAX_EDGE_NUM 500
#define min(a,b) a < b?a:b
struct Edge{
	int from;	//起点
	int to;	//终点
	int w;
	int next; //从from 出发的,下一条边的序号
	int rev; //该边的反向边 序号

	//用于查找反向边
	bool operator == (const pair<int,int>& p){
		return p.first == from && p.second == to;
	}
};

Edge gEdges[MAX_EDGE_NUM];

int gHead[MAX_NODE];
int gPre[MAX_NODE];
int gPath[MAX_NODE];
int gGap[MAX_NODE];
int gDist[MAX_NODE];
int gFlow[MAX_NODE][MAX_NODE];

int gSource, gDestination;
int gEdgeCount;

void InsertEdge(int u, int v, int w){
	Edge* it = find(gEdges, gEdges + gEdgeCount, pair<int, int>(u, v));
	if (it != gEdges + gEdgeCount){ //如果已经有边 u --> v,则之前肯定已经指定了 u-->v 和 v-->u的反向关系
		it->w += w;
	}
	else{ //添加 u-->v的边和反向边 v --> u
		int e1 = gEdgeCount;
		gEdges[e1].from = u;
		gEdges[e1].to = v;
		gEdges[e1].w = w;
		gEdges[e1].next = gHead[u];
		gHead[u] = e1;

		gEdgeCount++;

		int e2 = gEdgeCount;
		gEdges[e2].from = v;
		gEdges[e2].to = u;
		gEdges[e2].w = 0;
		gEdges[e2].next = gHead[v];
		gHead[v] = e2;

		//指定各个边的反向边
		gEdges[e1].rev = e2;
		gEdges[e2].rev = e1;

		gEdgeCount++;
	}
	gFlow[u][v] = w;
}

//使用bfs进行分层,标记每个点到终点的距离
void Bfs(){
	memset(gGap, 0, sizeof(gGap));
	memset(gDist, -1, sizeof(gDist));

	queue<int> Q;
	gGap[0] = 1;
	gDist[gDestination] = 0;
	Q.push(gDestination);
	while (!Q.empty()){
		int n = Q.front();
		Q.pop();
		for (int e = gHead[n]; e != -1; e = gEdges[e].next){
			int v = gEdges[e].to;
			if (gDist[v] >= 0){ //gDist初始值为-1. 如果>= 0,说明之前已经被访问过了
				continue;
			}
			gDist[v] = gDist[n] + 1;
			gGap[gDist[v]] ++;
			Q.push(v);
		}
	}
}

int ISPA(int n){ //n 为顶点的个数
	int ans = 0, u = gSource, d, e;
	while (gDist[gSource] <= n){
		if (u == gDestination){//进行增广
			int min_flow = INFINITE;
			for (e = gPath[u]; u != gSource; e = gPath[u = gPre[u]]) //找到路径中最小的边流量
				min_flow = min(min_flow, gEdges[e].w);

			for (e = gPath[u = gDestination]; u != gSource; e = gPath[u = gPre[u]]){ //增广操作
				gEdges[e].w -= min_flow;
				gEdges[gEdges[e].rev].w += min_flow;
				gFlow[gPre[u]][u] += min_flow;
				gFlow[u][gPre[u]] -= min_flow;
			}
			ans += min_flow;
		}

		for (e = gHead[u]; e != -1; e = gEdges[e].next){
			if (gEdges[e].w > 0 && gDist[u] == gDist[gEdges[e].to] + 1)
				break;
		}
		if (e >= 0){	//可以向前找到一点,继续扩展
			gPre[gEdges[e].to] = u;
			gPath[gEdges[e].to] = e;
			u = gEdges[e].to;
		}
		else{
			if (--gGap[gDist[u]] == 0){ //gap 优化
				break;
			}
			for (d = n, e = gHead[u]; e != -1; e = gEdges[e].next){ //重标号
				if (gEdges[e].w > 0)
					d = min(d, gDist[gEdges[e].to]);
			}

			gDist[u] = d + 1;
			++gGap[gDist[u]];
			if (u != gSource) //回溯
				u = gPre[u];

		}
	}
	return ans;
}

int main(){
	int u, v, w;
	int n, m;

	while (scanf("%d %d", &m, &n) != EOF){
		gEdgeCount = 0;
		memset(gHead, -1, sizeof(gHead));
		for (int i = 0; i < m; i++){
			scanf("%d %d %d", &u, &v, &w);
			InsertEdge(u, v, w);
		}
		gSource = 1;
		gDestination = n;

		Bfs();
		int result = ISPA(n);
		printf("%d\n", result);
	}
	return 0;
}
时间: 2025-01-01 15:42:55

网络最大流算法的相关文章

网络最大流算法—Dinic算法及优化

前置知识 网络最大流入门 前言 Dinic在信息学奥赛中是一种最常用的求网络最大流的算法. 它凭借着思路直观,代码难度小,性能优越等优势,深受广大oier青睐 思想 $Dinic$算法属于增广路算法. 它的核心思想是:对于每一个点,对其所连的边进行增广,在增广的时候,每次增广“极大流” 这里有别于EK算法,EK算法是从边入手,而Dinic算法是从点入手 在增广的时候,对于一个点连出去的边都尝试进行增广,即多路增广 Dinic算法还引入了分层图这一概念,即对于$i$号节点,用$dis(i)$表示它

网络最大流算法—最高标号预留推进HLPP

吐槽 这个算法.. 怎么说........ 学来也就是装装13吧.... 长得比EK丑 跑的比EK慢 写着比EK难 思想 大家先来猜一下这个算法的思想吧:joy: 看看人家的名字——最高标号预留推进 多么高端大气上档次2333333咳咳 从它的名字中我们可以看出,它的核心思想是—推进,而不是找增广路 那么它是怎么实现推进的呢? 很简单,我们从源点开始,不停的向其他的点加流量,对于每个点都如此操作.那么推到最后,我们就可以得到到达汇点的最大流量 不过可能会出现一种情况,就是$A$送流量给$B$,$

算法9-5:最大流算法的Java代码

残留网络 在介绍最大流算法之前先介绍一下什么是残留网络.残余网络的概念有点类似于集合中的补集概念. 下图是残余网络的例子.上面的网络是原始网络,下面的网络是计算出的残留网络.残留网络的作用就是用来描述这个网络中还剩下多少可以利用的流量. 流量网络 最大流算法比以前介绍的算法都要复杂.网络中的每一条边需要记录容量和当前流量.容量是固定值,是已知条件,而当前流量在计算过程中会一直发生变化.因此,需要建立一个专门的类,用于最大流算法. public class FlowEdge { private i

算法模板——Dinic网络最大流 2

实现功能:同Dinic网络最大流 1 这个新的想法源于Dinic费用流算法... 在费用流算法里面,每次处理一条最短路,是通过spfa的过程中就记录下来,然后顺藤摸瓜处理一路 于是在这个里面我的最大流也采用这种模式,这样子有效避免的递归,防止了爆栈么么哒 1 type 2 point=^node; 3 node=record 4 g,w:longint; 5 next,anti:point; 6 end; 7 var 8 i,j,k,l,m,n,s,t,flow:longint; 9 a,e:a

Ural1109_Conference(二分图最大匹配/匈牙利算法/网络最大流)

解题报告 二分图第一题. 题目描述: 为了参加即将召开的会议,A国派出M位代表,B国派出N位代表,(N,M<=1000) 会议召开前,选出K队代表,每对代表必须一个是A国的,一个是B国的; 要求每一个代表要与另一方的一个代表联系,除了可以直接联系,也可以电话联系,求电话联系最少 思路: 电话联系最少就要使直接联系最大,又是一一匹配关系,就是二分图的最大匹配. 下面是匈牙利算法. #include <cstdio> #include <cstring> #include <

【算法】网络最大流 Dinic

Dinic的大体思路是和EK差不多的(其实很多算法的大体思路都一样),只不过Dinic在每次寻找增广路时先bfs一下,给每个点都加上一个等级,而规定:只有等级相邻的两个点之间才能走,那么在dfs时就会减掉很多无用因此不必要的道路 1 #include<algorithm> 2 #include<iostream> 3 #include<cstring> 4 #include<cstdio> 5 #include<queue> 6 using na

【算法】网络最大流 EK

网络流是干嘛的?举一个例子: 在一个水上城市中,有很多小镇,之间有很多座桥连着,每一座桥因为制作材料不同最大载重不同,如果超过最大载重,桥就垮了,桥上的人就GG了,所以我们不能让这样的情况发生——即:每一条边的流量不能超过容量,我们再规定一个起点,一个终点,我们要从起点运货到终点,只有一次机会但可以同时走多条道路充分利用资源,最后求:最大运货量可以为多少? 这就是网络最大流问题,求某点到某点的最大流量. EK算法,网络流最朴素的算法,不断寻找增广路,再来回两遍减容量,加ans,容易理解: 1 #

网络最大流 dinic算法

一句话题意:给出一个网络图,以及其源点和汇点,求出其网络最大流 //dinic算法; //时间复杂度O(V^2E); #include<bits/stdc++.h> #define inf 999999 #define maxn 200000 using namespace std; int n,m,s,t; int ans=0; struct Edge { int to,next,w; }; struct Edge edge[maxn]; int head[maxn],val[maxn],p

网络最大流的(Edmond Karp)算法

原来一听到网络最大流啊,什么BFS,DFS的就感觉特别的陌生,也感觉特别的头疼,如今我终于要学习到这里了这也标志着我要真正的要学习算法和搞acm了,所以我更要努力的学习力求向上把它学好. 不废话了,这最大流问题通过我今天的学习和理解终于有点眉目了,我就做个随笔,首先了解一下容量网络,百度了一下:         容量网络:在有向图D=(V,A),指定一个点为发点,记作         ,指定另一个点为收点,记作         ,其余点叫作中间点.对于A的每条弧(         ),都对应一个