最大流算法——预流推进


http://xingzheqiang.blog.163.com/blog/static/20561012520127464654159/



背景知识不明者找Google。

-----------------------------------------------------------------------------

先简单看一下主过程://一看如此简单,先吓一跳。


int MAXFLOW(){
    hights();
    prepare();
    while (!Q.empty()) {
       u = Q.get;
       for each e in G' (from u) push(e);
      if (!fixed(u)) reCalc(u);
   }
}


接下来介绍算法
预流推进算法给每一个顶点一个标号h(v),表示该点到t的最短路(在残量网络中)。
第一步hights()过程,就是BFS出初始最短路,计算出每一个结点的h(v)。//可以看作是汇点有“吸力”,使每个结点有不同的负压,在“负压”作用下把来自源点的流吸过去。

预流推进算法的特征是运用了预流来加快运算。预流说明图中的结点(除s, t),仅需要满足流入量 >= 流出量。其中流入量>流出量的结点,我们称之为活动结点。/*换句话说就是有流可吸,还没吸到地方。*/我们的算法就是不断地将活动结点,变为非活动结点,使得预流成为可行流。

算法过程prepare(),即首先将与s相连的边设为满流,并将这时产生的活动结点加入队列Q。这是算法的开始。
以后便重复以下过程直到Q为空:
(1).选出Q的一个活动结点u。并依次判断残量网络G'中每条边(u, v),若h(u) = h(v) + 1,则顺着这些边推流,直到Q变成非活动结点(不存在多余流量)。(Push推流过程)//同时把所有v加入活动结点的队列。
(2).如果u还是活动结点。则需要对u进行重新标号:h(u) = min{h(v) + 1},其中边(u,v)存在于G' 中。然后再将u加入队列。(reCalc过程)//后面都满流时就吸不动了,负压自然也要重新计算。

可以证明,通过以上算法得到的结果就是最大流。

//显然每次循环后标号和残量网络都是相容的。算法结束时Q为空,只可能是没有活动结点。因为一开始就把从源所有的流推了出来,只可能是要么能够推到汇要么最后退回源。显然,一开始源的标号最高,退回源说明源汇之间已被切断,否则总能杀出一条增广路来。

如果该算法的Q是标准的FIFO队列,则时间复杂度为(n2m),/*最高标号不会超过n(超过时必无到汇的路径),所以n个点每个最多重新标号n次,两次标号之间m条边每条最多推流一次。*/如果是优先队列,并且标号最高的点优先的话,我们就得到了最高标号预流推进算法,其时间复杂度仅为(n2sqrt(m)),/*复杂度分析进行中……*/算是比较快的最大流算法了。


添加一个示例代码:

http://kenby.iteye.com/blog/945483


#include 
#include 

#define DEBUG

#ifdef DEBUG
#define debug(...) printf( __VA_ARGS__) 
#else
#define debug(...)
#endif

#define N 102
#define MAX_INT 2000000

#define min(a, b) ((a) < (b) ? (a) : (b))

int		graph[N][N];
int		h[N];
int		e[N];
int		n;

int push_relabel(int s, int t)
{
	int 	max_flow, u, v, d, done, relabel, min_height;

	memset(h, 0, sizeof(h));
	memset(e, 0, sizeof(e));

	h[s] = n;
	for (u = 1; u <= t; u++) {
		if (graph[s][u] > 0) {
			e[u] = graph[s][u];
			e[s] -= graph[s][u];
			graph[u][s] = graph[s][u];
			graph[s][u] = 0;
		}
	}

	for (;;) {
		done = 1;
		for (u = s+1; u < t; u++) {
			if (e[u] > 0) {
				done = 0;
				//先假设顶点u需要relabel
				relabel = 1;
				for (v = s; v <= t && e[u] > 0; v++) {	/* 检查能push的顶点 */
					if (graph[u][v] > 0 && h[u] > h[v]) {
						//push
						relabel = 0;
						d = min(graph[u][v], e[u]);
						e[u] -= d;
						e[v] += d;
						graph[u][v] -= d;
						graph[v][u] += d;
						debug("push %d --%d--> %d, e[%d] = %d\n", u, d, u, u, e[u]);
					}
				}
				//没有可以push的顶点,执行relabel
				if (relabel) {
					//relabel
					min_height = MAX_INT;
					for (v = s; v <= t; v++) {
						if (graph[u][v] > 0 && h[v] < min_height) {
							min_height = h[v];
						}
					}
					h[u] = 1 + min_height;
					debug("relabel %d height to %d\n", u, h[u]);
				}
			}
		}
		if (done) {	/* 除源点和汇点外,每个顶点的e[i]都为0 */
			max_flow = 0;
			for (u = s; u <= t; u++) {
				if (graph[t][u] > 0) {
					max_flow += graph[t][u];
				}
			}
			break;
		}
	}
	return max_flow;
}

int main()
{
	int		np, nc, m, u, v, w;

	while (scanf("%d", &n) != EOF) {
		n += 2;		/* 添加源点和汇点 */
		scanf("%d %d %d", &np, &nc, &m);
		memset(graph, 0, sizeof(graph));
		//输入m条边
		while (m--) {
			scanf(" (%d,%d)%d", &u, &v, &w);
			graph[u+1][v+1] = w;
		}
		//输入np个power station
		while (np--) {
			scanf(" (%d)%d", &u, &w);
			graph[0][u+1] = w;
		}
		//输入nc个consumer
		while (nc--) {
			scanf(" (%d)%d", &u, &w);
			graph[u+1][n-1] = w;
		}
		printf("%d\n", push_relabel(0, n-1));
	}
	return 0;
}



你可能感兴趣的:(学习笔记)