网络流学习(转载)
转载:
http://jijiwaiwai163.blog.163.com/blog/static/186296211201262225242674/
http://blog.csdn.net/ruleless/article/details/7876056
http://blog.csdn.net/allenjy123/article/details/6661491
算法名称 |
复杂度 |
概要 |
增广路方法 |
||
一般增广路算法 |
O(nmU) |
在残留网络中,每次任意找一条增广路径增广。 |
容量缩放增广路算法 |
O(nm logU) |
在残留网络中,每次找一条有最大可增广容量和的增广路径增广,即残留网络中源到汇的最长路。 |
最短增广路算法 |
O(nm2) |
在残留网络中,每次找一条含结点数最少的增广路增广,即残留网络中源到汇的BFS 路径。 |
连续最短增广路算法 |
O(n2m) |
在 Edmonds Karp 的基础上改造。在每次BFS 找增广路时,记录每个点的距离标号。在距离标号的所构成的最短路图上,不断地 DFS找增广路。即一次标号多次增广,以提高速度。 |
预流推进方法 |
||
一般预流推进算法 |
O(n2m) |
维护一个预流,不断地对活跃结点执行push操作或relabel操作来调整这个预流,直到不能操作。 |
先进先出预流推进算法 |
O(n3) |
以先进先出队列维护活跃结点。 |
最高标号预流推进算法 |
O(n2m1/2) |
每次检查具有最高标号的活跃结点。 |
我们将对基于有向带权图的模型做进一步扩展。
很多系统中涉及流量问题,例如公路系统中车流量,网络中的数据信息流,供油管道的油流量等。我们可以将有向图进一步理解为“流网络”(flow network),并利用这样的抽象模型求解有关流量的问题。
图 电路原理图可抽象为网络流
流网络中每条有向边可以认为是传输物质的管道,每个管道有固定的容量,可以看作是物质能够流经该管道的最大速度。顶点是管道之间的交叉连接点,除了汇点之外,物质只流经这些点,不会再顶点滞留或消耗。也就是说,物质进入某顶点的速度必须等于离开该顶点的速度。这一特性被称为“流守恒”(flow conservation)。例如图中的电路原理图,根据基尔霍夫电流定律,在每个交叉连接点出,流进的电流等于流出的电流。电流的定义为单位时间内通过导线某一截面的电荷量,即为电荷的流动速度。所以,用流守恒的观点可以理解为:电荷量流进某交叉顶点的速度等于离开该顶点的速度。
在本章我们将讨论最大流问题,这是流网络中最简单的问题:在不违背容量限制的条件下,求解把物质从源点传输到汇点的最大速率。本章主要介绍流网络和流的基本概念和性质,并提供流网络的数据结构描述和实现,以及一种解决最大流的经典方法及其算法实现,即Ford-Fulkerson方法。
.1 流网络
网络流G=(v, E)是一个有向图,其中每条边(u, v)均有一个非负的容量值,记为c(u, v) ≧ 0。如果(u, v) ? E则可以规定c(u, v) = 0。网络流中有两个特殊的顶点,即源点s和汇点t。
与网络流相关的一个概念是流。设G是一个流网络,其容量为c。设s为网络的源点,t为汇点,那么G的流是一个函数f:V×V →R,满足一下性质:
l 容量限制:对所有顶点对u,v∈V,满足f(u, v) ≦ c(u, v);
l 反对称性:对所有顶点对u,v∈V,满足f(u, v) = - f(v, u);
l 流守恒性:对所有顶点对u∈V-{s, t},满足Σv∈Vf(u,v)=0。
f(u, v)称为从顶点u到顶点v的流,流的值定义为:
|f| =Σv∈Vf(s,v),
即从源点s出发的总的流。
在最大流问题中,我们需要求解源点s到汇点t之间的最大流f(s, t),同时我们还希望了解达到该值的流。对于一个指定的源点s和指定汇点t的网,我们称之为st-网。
如图所示为一个流网络,其中顶点之间的边的粗细对应着边的容量大小。
图 有向图表示网络流
下面以图为例,在流的三个性质条件下尝试性地寻找图中的最大流,如图(a~c)。
从上图(a~c)中可以发现,流网络从源点s流出的量依次为2,3,5,而流入汇点t的流量也2,3,5。事实上,任何流从s流出量总应该等于到汇点t的流入量,下面对这一命题做简单证明。
构造:如图(a),对原流网络做扩展,增加顶点s’和一条边(s’, s),边的流和容量都与从s流出的流的值相等;增加顶点t’和一条边(t, t’),边的流和容量都与到t的流的值相等。
我们要证明s’的流出量等于t’的流入量,只要证明对任意顶点集合,流出量等于流入量即可。采用归纳证明。
证明:对于单个顶点构成的顶点集合,其流出量必然等于流出量;假设,对于一给定的顶点集合A此属性成立,则需要验证增加一个顶点v后得到的新的集合A’=A∪{v}也满足此属性。
如图,对集合A,从v流入的流记为f3,其它的流入量合计为f1;流出到v的流记为f4,其它的流出流量合计为f6。注意,这里的流都指的是流的值,都是非负的。
A的流入量为fin(A) = f1 + f3,流出量为fout(A) = f2 + f4;根据假设可以得出关系:
f1 + f3 = f2 + f4;
对顶点v,根据流的第二条性质,得出关系:
f6 + f3 = f5 + f4。
根据上面两个等式,可以得到关系:
f1 – f6 = f2 – f5,
即:
f1 + f5 = f2 + f6。
A’的流入量fin(A’) = f1 + f5,流出量fout(A’) = f2 + f6,所以集合A’满足属性。
将这个属性应用于扩展前的原始流网络中的所有顶点,可以得出边(s’, s)上的流等于边(t’, t)上的流,也就是从s流出量等于到汇点t的流入量。
.2 Ford-Fulkerson方法
本节开始讨论解决最大流问题的Ford-Fulkerson方法,该方法也称作“扩充路径方法”,该方法是大量算法的基础,有多种实现方法。在以后章节中我们将介绍并分析一种特定的算法。
Ford-Fulkerson算法是一种迭代算法,首先对图中所有顶点对的流大小清零,此时的网络流大小也为0。在每次迭代中,通过寻找一条“增广路径”(augument path)来增加流的值。增广路径可以看作是源点s到汇点t的一条路径,并且沿着这条路径可以增加更多的流。迭代直至无法再找到增广路径位置,此时必然从源点到汇点的所有路径中都至少有一条边的满边(即边的流的大小等于边的容量大小)。
这里提及一个新的概念,即“增广路径”。下面我们将进一步引入“残留网络”(residual network)来讨论增广路径的寻找算法,并引入“最大流最小割”(Max-Flow Min Cut)定理来证明Ford-Fulkerson算法的正确性。
.2.1 残留网
给定一个流网络G和一个流,流的残留网Gf拥有与原网相同的顶点。原流网络中每条边将对应残留网中一条或者两条边,对于原流网络中的任意边(u, v),流量为f(u, v),容量为c(u, v):
l 如果f(u, v) > 0,则在残留网中包含一条容量为f(u, v)的边(v, u);
l 如果f(u, v) < c(u, v),则在残留网中包含一条容量为c(u, v) - f(u, v)的边(u, v)。
残留网允许我们使用任何广义图搜索算法来找一条增广路径,因为残留网中从源点s到汇点t的路径都直接对应着一条增广路径。以图为例,具体分析增广路径及其相应残留网,如图(a~d)。
(a)原始图流网络,每条边上的流都为0。因为f(u, v) = 0 < c(u, v),则在残留网中包含容量为c(u, v)的边(u, v),所以此时残留图中顶点与原始流网络相同,边也与原始流网络相同,并且边的容量与原始流网络相同。
在残留网中可以找到一条增广路径<v0, v1, v3, v5>,每条边的流为2,此原始流网络和残留网中相应的边会有所变化,如下图。
(b)在操作(a)之后,路径<v0, v1, v3, v5>上有了大小为2的流,此时需要对残留图中相应的边做调整:
f(0, 1) > 0,在残留图中有容量为2的边(1, 0);
c(1, 3) > f(1, 3) > 0,在残留图中有容量为1的边(1, 3)和容量为2的边(3, 1);
f(3, 5) > 0,在残留图中有容量为2的边(5, 3).
在残留网中可以找到一条增广路径<v0, v2, v4, v5>,每条边的流为1,此原始流网络和残留网会有所变化,如下图。
(c)在操作(b)之后,路径<v0, v2, v4, v5>上有了大小为1的流,此时需要对残留图中相应的边做调整:
c(0, 2) > f(0, 2) > 0,在残留图中有容量为2的边(0, 2)和容量为1的边(2, 0);
f(2, 4) > 0,在残留图中有容量为1的边(4, 2);
c(4, 5) > f(4, 5) > 0,在残留图中有容量为2的边(4, 5)和容量为1的边(5, 4).
进一步在残留网中可以找到一条增广路径<v0, v2, v3, v1, v4, v5>,每条边的流为1,此原始流网络和残留网会有所变化,如下图。
(d)在操作(c)之后,路径<v0, v2, v3, v1, v4, v5>上有了大小为1的流,此时需要对残留图中相应的边做调整:
c(0, 2) > f(0, 2) > 0,在残留图中有容量为1的边(0, 2)和容量为2的边(2, 0);
f(2, 3) > 0,在残留图中有容量为1的边(3, 2);
c(3, 1) > f(3, 1) > 0,在残留图中有容量为1的边(3, 1)和容量为2的边(1, 3);
f(1, 4) > 0,在残留图中有容量为1的边(4, 1);
c(4, 5) > f(4, 5) > 0,在残留图中有容量为1的边(4, 5)和容量为2的边(5, 4);
此时残留图中无法再找到顶点0到顶点5的路径,则迭代结束,我们认为图d中即为寻找到的最大流。
2 最大流最小割
我们刚刚讨论了基于残留网的增广路径的寻找方法,这里我们将证明Ford-Fulkerson算法迭代停止时得到的流是最大流,即一个流是最大流,当且仅当残留网中不包含增广路径。该命题的证明需要借助于流网络中的一个重要定理,即最大流最小割定理。
流网络G=(V, E)的割(S, T)将V分为S和T=V-S两个部分,使得源点s∈S,汇点t∈T。如果f是一个流,则穿过割(S, T)的流用f(S, T) = Σu∈SΣv∈T f(u, v)表示,割(S, T)的容量用C(S, T) = Σu∈SΣv∈T c(u, v)。如图,流网络的一个割为({s, v1, v2},{v3, v4, t})
图 (a)流网络每条边上是容量大小 (b)流网络的一个割,边上是流的大小
通过该割的流量为:
f(S, T) = Σu∈{s, v1, v2}Σv∈{v3, v4, t} f(u, v)
= f(v1, v3) + f(v2, v3) + f(v2, v4)
= 12 + (-4) + 11 = 19
容量为:
C(S, T) = Σu∈{s, v1, v2}Σv∈{v3, v4, t} c(u, v)
= c(v1, v3) + c(v2, v4)
= 12 + 14 = 26
其中割的流可能是正数也可能是负数,而容量一定是非负的。在流网络中,每个割的流都是相同的,其值等于流网络的流的值;并且每个割的流都不大于割的容量。
如图,s’为扩展的顶点,其中边(s’, s)的流和容量都等于顶点s的流出量,记为f1。虚线将流网络分为两个集合S和T,形成割(S, T)。从S流出的流量为f2,流入S的流量为f3。在第一节中我们证明了流网络中的顶点集合的流入量等于流出量,所以f1 + f2 = f3。
即f1 = f3 – f2,其中f1等于流网络的流的值,f3-f2为割(S, T)的流量,所以,割的流等于流网络的流的值。
在上图中,计算割(S, T)的流量时f3的提供正的流量值,而f2提供的是负的流量值,并且在计算割的容量时只有提供流量f3的边的容量参与相加,根据流的第一条性质,f3的值不会大于割的容量,所以:
f(S, T) = f3 – f2 ≦ f3 ≦ C(S, T)。
由于流网络中所有割的流都相等并且等于网络的流,所有网络的任何流的值都不大于任何一个割的容量。
根据上面对流网络的中割的概念的介绍,下面引入最大流最小割定理,并利用该定理说明Ford-Fulkerson算法的正确性。
最大流最小割定理:一个网中所有流中的最大值等于所有割中的最小容量。并且可以证明一下三个条件等价:
l f是流网络G的一个最大流;
l 残留网Gf不包含增广路径;
l G的某个割(S, T),满足f(S, T) = c(S, T).
证明:
1.(反证法)假设f是G的最大流,但是Gf中包含增广路径p。显然此时沿着增广路径可以继续增大网络的流,则f不是G的最大流,与条件矛盾;
2.假设Gf中不包含增广路径,即Gf中不包含从s到t的路径。定义:
S = {v∈V:Gf中包含s到v的路径},
令T = V – S,由于Gf中不存在从s到t的路径,则t?S,所以得到G的一个割(S, T)。对每对顶点u∈S,v∈T,必须满足f(u, v) = c(u, v),否则边(u, v)就会存在于Gf的边集合中,那么v就应当属于S(而事实上是v∈T)。所以,f(S, T) = c(S, T);
3.我们已经证明,网络的任何流的值都不大于任何一个割的容量,如果G的某个割(S, T),满足f(S, T) = c(S, T),则说明割(S, T)的流达到了网络流的上确界,它必然是最大流。
Ford-Fulkerson算法的迭代终止条件是残留网中不包含增广路径,根据上面的等价条件,此时得到的流就是网络的最大流。
.3 Ford-Fulkerson方法的实现
在前一节,我们讨论了Ford-Fulkerson方法中所应用到的几个概念以及保证该方法正确性的重要属性。本节将讨论Ford-Fulkerson方法的具体实现,包括残留网的更新和增广路径的获取。
增广路径事实上是残留网中从源点s到汇点t的路径,可以利用图算法中的任意一种被算法来获取这条路径,例如BFS,DFS等。其中基于BFS的算法通常称为Edmonds-Karp算法,该算法是“最短”扩充路径,这里的“最短”由路径上的边的数量来度量,而不是流量或者容量。
这里所选的路径寻找方法会直接影响算法的运行时间,例如,对图采用DFS的方法搜索残留网中的增广路径。图(b)中是第一次搜索得到的增广路径为<s, v1, v2, t>,路径的流大小为1;图(c)和(d)中搜索得到的增广路径的流大小也是1。可以发现,在这个例子中,采用DFS算法将需要2000000次搜索才能得到最大流。
如果换一种方法对残留网中的进行遍历将会很快求得流网络的最大流。如图,第一次在顶点1搜索下一条边时,不是选择边(1, 2)而是选择容量更大的边(1, t);第二次在顶点2处搜索下一条边时,选择边(2, t)。这样只要两次遍历即可求解最大流。可见,在残留网中搜索增广路径的算法直接影响Ford-Fulkerson方法实现的效率。
3.2 优先队列搜索
本节将讨论有向带权图的一个新的搜索算法,称为基于优先队列的图搜索算法。首先将介绍基于下标堆得优先队列数据结构,并在下文介绍利用该数据结构对Ford-Fulkerson算法的改进。
3.2.1 基于下标堆的优先队列
本节首先介绍基于下标对的优先队列数据结构。假设要在优先队列中处理的记录在一个已存在的数组中,可以让优先队列例程通过数组下标来引用数据项。这样队列中只需要数组的下标,所有对优先队列的操作都是对数组下标的操作。这里之所以要讨论这种优先队列,主要是因为在图数据结构中我们使用顶点的标号来访问顶点,我们可以将顶点的标号作为优先队列中的元素项,通过这种映射方式可以更高效地利用优先队列处理有向带权图。
这里基于下标的优先队列与前面章节中讨论的优先队列的基本操作类似,读者可以温习一下前面关于堆和优先队列的内容。基于下标的优先队列的实现如下:
// 存放元素内容的数组
private ElemItem[] a;
// 序号的优先队列,元素的优先级
private int[] pq, qp;
// 元素总数
private int N;
// 类型,-1表示最大堆;1表示最小堆。
private int type;
/**
* 构造函数
* @param items 元素项数组
*/
public intPQi(ElemItem[] items, int type){
a = items; N = 0;
pq = new int[a.length + 1];
qp = new int[a.length + 1];
this.type = type;
}
/**
* 比较a[i]和a[j]
* @param i, j 第i, j个元素
* @return type = -1时,
* 如果a[i]小于a[j]返回true,否则false
*/
private boolean less(int i, int j){
int c = a[pq[i]].compareTo(a[pq[j]]);
return c * type > 0;
}
/**
* 交换a[i]和a[j]
* @param i, j 第i, j个元素
*/
private void exch(int i, int j){
int t = pq[i];
pq[i] = pq[j];
pq[j] = t;
qp[pq[i]] = i;
qp[pq[j]] = j;
}
/**
* 将a[k]向上移
* @param k 表示待移动的是a[k]
* 函数将元素a[k]移动到正确的位置,使得a[k]
* 比其子节点元素大。
*/
private void swim(int k){
while(k > 1 && less(k / 2 , k)){
exch(k, k / 2);
k = k / 2;
}
}
/**
* 自顶向下堆化,将a[k]逐渐下移
* @param k 表示代移动的是a[k]a
* @param N 表示元素总个数为N
* 函数将元素a[k]移动到正确的位置
*/
private void sink(int k, int N){
while(2 * k <= N){
int j = 2 * k;
if(j < N && less(j, j + 1)) j++;
if(!less(k, j)) break;
exch(k, j);
k = j;
}
}
//判断当前队列是否为空
public boolean empty(){
return N == 0;
}
/**
* 插入一个新的元素,插入的位置为v
*/
public void insert(int v){
pq[++N] = v;
qp[v] = N;
swim(N);
}
/**
* 获取(删除)当前最大的元素
* @return 当前最大的元素
*/
public int getmax(){
exch(1, N);
sink(1, N - 1);
return pq[N--];
}
// 改变第k个元素
public void change(int k){
swim(qp[k]);
sink(qp[k], N);
}
// 调整第k个元素在堆中的位置
public void lower(int k){
swim(qp[k]);
}
}
其中元素项数组a为指向队列中处理的记录对应的数组的指针,称这里的数组为客户数组。数组pq为指向用户数组中元素的下标数组,堆中第i个位置处对应着客户数组中第pq[i]个元素,用a[pq[i]]来访问客户数组中对应的元素。数组qp为客户数组中各元素的优先级,qp[j]表示客户数组中第j的元素项的优先级为qp[j],那么优先队列中第i个位置对应的数组元素的优先级为pq[pq[i]]。在这里我们对堆中每个位置的优先级的量化为:队列中第i个位置对应数组元素的优先级为i,也就是说qp[pq[i]]=i。这里有一个新的成员变量type,该变量决定了堆的类型。
在less函数中,首先应用函数compareTo比较客户数组中两个元素a[i]和a[j],比较结果为c。如果a[i]<a[j]则c<0,若此时type=-1,则c*type>0,less函数返回true;反之,若此时type=1,则c*type<0,less函数返回false。所以在type=-1时,这里的less函数与之前中讨论的最大堆中的less函数功能相同。事实上,这里的type取值-1表示最大堆,反之取值1表示最小堆。由于less函数在处理队列的其它函数中都有调用,下面我们以最大堆为例,即type=-1,进行讨论。
函数swim将队列中指定k位置对应的客户数组的下标向上移动到正确的位置,直到其父节点处对应的元素值不比它小为止。sink函数的过程与之相反,是将k位置上的对应的下标向下移动到合适的位置。getmax函数返回队列顶部对应的客户数组的下标,在最大堆中,该下表对应着客户数组中的最大项。
3.2.2 PFS搜索增广路径
接下来将介绍一种Ford-Fulkerson算法的实现,该算法沿着可以使流得到最大增长的路径进行扩充,可以利用基于下标堆的优先队列来实现。在图中的示例就是基于这个思路。在算法中用数组wt记录每个能提供的流(的相反数),数组st记录与每个顶点相关联的提供最大流的边。算法的实现如下:
* 优先级优先遍历函数;
* 函数搜索网络起点s至终点t的最大流路径。
*/
private boolean PFS(){
int M = -1 * Integer.MAX_VALUE;
// 基于下标堆(最小堆)的优先队列
intPQi pQ = new intPQi(wt, 1);
for(int v = 0; v < G.get_nv(); v++){
wt[v] = new ElemItem<Integer>(0);
st[v] = null;
pQ.insert(v);
}
// 起点s置于优先队列顶部
wt[s] = new ElemItem<Integer>(M);
pQ.lower(s);
// 迭代过程,寻找流量最大的路径
while(!pQ.empty()){
// 堆顶顶点号,getmax返回最小
int v = pQ.getmax();
wt[v] = new ElemItem<Integer>(M);
// v到达终点或者st[v]为空则推出迭代
if(v == t) break;
if(v != s && st[v] == null) break;
// 更新v的所有相邻顶点在扩充路径上的流
for(NetworkEdge E = G.firstEdge(v);
G.isEdge(E); E = G.nextEdge(E)){
NetworkEdge TmpE = E;
// 如果E的容量为负,则将E更新为E的反向边
if(G.getEdgeC(E) < 0){
E = G.getNetworkEdge(E.get_v2(), E.get_v1());
}
if(E == null) return false;
// 获取E的另一顶点w
int w = E.other(v);
// 获取顶点w在扩充路径上的流
int cap = G.capRto(E, w);
int wt_v = ((Integer)(wt[v].getElem())).intValue();
int P = cap < (-1 * wt_v)?cap:(-1 * wt_v);
int wt_w = ((Integer)(wt[w].getElem())).intValue();
if(cap > 0 && (-1 * P) < wt_w){
// 更新顶点w在扩充路径上的流
wt[w] = new ElemItem<Integer>(-1 * P);
// 更新优先队列
pQ.lower(w);
st[w] = E;
}
E = TmpE;
}
}
System.out.println("--------------------------");
for(int k = 0; k < st.length; k++ ){
if(st[k] != null)
System.out.println(st[k].get_v1()
+ "-" + st[k].get_v2());
}
return st[t] != null;
}
算法中利用的下标堆优先队列中使用了最小堆,队列的客户数组为wt,算法按照能提供的流由大到小的顺序取出队列中的顶点v。获取所有与顶点v相关联的边,这些边不仅包括以v为源点的边,还包括以v为终点的边。然后对每条边上的另一顶点w(相对于顶点v)所能提供的流的大小wt[w]以及对应的边st[w]。一旦顶点v找不到相关联的边则函数返回false,即找不到增广路径。
在流网络中。访问与顶点v相关联的边时,我们只能通过firstEdge和nextEdge迭代访问以顶点v为源点的边。但是在算法中我们还需要访问以v为终点的边,这需要对原始流网络做技巧性的调整。我们给原始流网络中的每一条边预留一条反向的边,这条边的容量为-1。如果源点为v的某条边E的容量G.getEdgeC(E) < 0,则将边E反向即可获得对应的以v为终点的边。
.3.3 流增广过程
基于PFS搜索得到的st数组,我们可以得到各个顶点相关联的能提供最大流的边,根据这些边形成的增广路径可以增加网络流。流网络源点为s,汇点为t,则从t开始,更新边(st[t], t),然后继续向顶点s迭代直到到达顶点s。算法实现如下:
int d = G.capRto(st[t], t);
for(int v = ST(t); v != s;
v = ST(v)){
int tt = G.capRto(st[v], v);
if(G.capRto(st[v], v) < d)
d = G.capRto(st[v], v);
}
G.addflowRto(st[t], t, d);
for(int v = ST(t); v != s; v = ST(v))
G.addflowRto(st[v], v, d);
}
.3.4 基于PFS的Ford-Fulkerson算法
结合PFS搜索过程和流增广过程可以实现高效的Ford-Fulkerson方法。该算法沿着可以使流得到最大增长的路径进行扩充。实现如下:
// 迭代
while(PFS()){
augument();
// 打印当前网络各边的网络流
for(int i = 0; i < G.get_nv(); i++){
for(NetworkEdge E = G.firstEdge(i);
G.isEdge(E); E = G.nextEdge(E)){
if(G.getEdgeFlow(E) > 0)
System.out.print(E.get_v1() +
" <-- " + G.getEdgeFlow(E) +
"/" +G.getEdgeC(E) +
" --> " + E.get_v2() + " ||\t");
}
System.out.println();
}
}
}
算法中迭代地运用PFS搜索残留图中的增广路径,并调用增广过程不断增加网络的流。以图为例,编写测试程序:
public static void main(String args[]){
Network N = new Network(6);
N.setEdgeC(0, 1, 2);
N.setEdgeC(0, 2, 3);
N.setEdgeC(1, 3, 3);
N.setEdgeC(1, 4, 1);
N.setEdgeC(2, 3, 1);
N.setEdgeC(2, 4, 1);
N.setEdgeC(3, 5, 2);
N.setEdgeC(4, 5, 3);
N.setEdgeC(1, 0, -1);
N.setEdgeC(2, 0, -1);
N.setEdgeC(3, 1, -1);
N.setEdgeC(4, 1, -1);
N.setEdgeC(3, 2, -1);
N.setEdgeC(4, 2, -1);
N.setEdgeC(5, 3, -1);
N.setEdgeC(5, 4, -1);
NetworkMaxFlow NF = new NetworkMaxFlow(N, 0, 5);
NF.Ford_Fulkerson();
}
}
PFS搜索得到的增广路径的边:
0-1
0-2
1-3
2-4
3-5
当前每条边上的流/容量:
0 <-- 2/2 --> 1 ||
1 <-- 2/3 --> 3 ||
3 <-- 2/2 --> 5 ||
PFS搜索得到的增广路径的边:
1-3
0-2
2-3
2-4
4-5
当前每条边上的流/容量:
0 <-- 2/2 --> 1 || 0 <-- 1/3 --> 2 ||
1 <-- 2/3 --> 3 ||
2 <-- 1/1 --> 4 ||
3 <-- 2/2 --> 5 ||
4 <-- 1/3 --> 5 ||
PFS搜索得到的增广路径的边:
1-3
0-2
2-3
1-4
4-5
当前每条边上的流/容量:
0 <-- 2/2 --> 1 || 0 <-- 2/3 --> 2 ||
1 <-- 1/3 --> 3 || 1 <-- 1/1 --> 4 ||
2 <-- 1/1 --> 3 || 2 <-- 1/1 --> 4 ||
3 <-- 2/2 --> 5 ||
4 <-- 2/3 --> 5 ||
PFS搜索得到的增广路径的边:
0-2
从结果可以看出,进过三次搜索便可以找出流网络中的最大流。每次都打印显示PFS搜索得到的增量路径以及网络中每条边上的流。
再以图为例,验证基于PFS算法的Ford-Fulkerson算法可以更高效:
N.setEdgeC(0, 1, 100);
N.setEdgeC(0, 2, 100);
N.setEdgeC(1, 2, 1);
N.setEdgeC(1, 3, 100);
N.setEdgeC(2, 3, 100);
N.setEdgeC(1, 0, -1);
N.setEdgeC(2, 0, -1);
N.setEdgeC(2, 1, -1);
N.setEdgeC(3, 1, -1);
N.setEdgeC(3, 2, -1);
NetworkMaxFlow NF = new NetworkMaxFlow(N, 0, 3);
NF.Ford_Fulkerson();
PFS搜索得到的增广路径的边:
0-1
0-2
1-3
当前每条边上的流/容量:
0 <-- 100/100 --> 1 ||
1 <-- 100/100 --> 3 ||
PFS搜索得到的增广路径的边:
0-2
2-3
当前每条边上的流/容量:
0 <-- 100/100 --> 1 || 0 <-- 100/100 --> 2 ||
1 <-- 100/100 --> 3 ||
2 <-- 100/100 --> 3 ||
PFS搜索得到的增广路径的边:
可见,基于PFS算法的Ford-Fulkerson方法的实现比基于DFS的实现效率更高。事实上,可以证明基于PFS算法的实现中所需要的增广路径搜索次数最多为V·E/2,而普通的Ford-Fulkerson方法所需的增广路径的搜索次数最多为V·M,其中M为流网络中最大的边容量,通常需要的搜索次数更多。
如果只从网络流的定义来看,它能求解的问题似乎很少,但如果考虑到转换的思想,则很多看起来与网络流丝毫不沾边的问题也能用网络流来解,这也是为什么网络流在ACM比赛中被列为较难题的原因所在。
我这里不打算讨论怎么对一个问题建立网络流模型,说句实在话,我也无从讨论这个。如果将适于用网络流解决的问题看成是一个集合,那么这个集合是趋于无穷的,如果按照某种规则对它们进行分类组成另外一个问题分类集,那么这个问题分类集也是趋于无穷的。所以,唯一的办法是训练自己对这类问题的敏锐性,然后具体问题具体分析。
Edmonds Karp求最大流的算法。
1 #define N 1000 2 #define Inf 9999999 3 4 int G[N][N],c[N][N],f[N][N],pre[N],que[N],n,vis[N]; 5 6 int ek(int s,int t) 7 { 8 int i,j,k,head,tail,flow=0; 9 memset(f,0,sizeof(f)); 10 while(true) 11 { 12 head=tail=0; 13 memset(vis,0,sizeof(vis)); 14 que[tail++]=s; 15 vis[s]=true; 16 while(head<tail) 17 { 18 k=que[head++]; 19 if(k==t) 20 break; 21 for(i=1;i<=n;i++) 22 { 23 if(c[k][i]>0&&!vis[i]) 24 { 25 vis[i]=true; 26 pre[i]=k; 27 que[tail++]=i; 28 } 29 } 30 } 31 if(k!=t) 32 break; 33 int cc=Inf; 34 j=t; 35 i=pre[j]; 36 while(j!=s) 37 { 38 if(c[i][j]<cc) 39 cc=c[i][j]; 40 j=i; 41 i=pre[j]; 42 } 43 flow+=cc; 44 j=t; 45 i=pre[j]; 46 while(j!=s) 47 { 48 f[i][j]+=cc; 49 f[j][i]=-f[i][j]; 50 c[i][j]=G[i][j]-f[i][j]; 51 c[j][i]=G[j][i]-f[j][i]; 52 j=i; 53 i=pre[j]; 54 } 55 } 56 return flow; 57 }
dinic求最大流,图用邻接矩阵表示。
1 #define N 1000 2 #define Min(a,b) a<b?a:b 3 #define Inf 9999999 4 int G[N][N],c[N][N],n,s,t; 5 int level[N],que[N]; 6 7 bool makelevel() 8 { 9 int i,j,k,head=0,tail=0; 10 memset(level,-1,sizeof(level)); 11 level[s]=0; 12 que[tail++]=s; 13 while(head<tail) 14 { 15 k=que[head++]; 16 for(i=1;i<=n;i++) 17 if(c[k][i]>0&&level[i]==-1) 18 { 19 level[i]=level[k]+1; 20 que[tail++]=i; 21 } 22 } 23 return level[t]!=-1; 24 } 25 26 int findpath(int u,int alpha) 27 { 28 if(u==t) 29 return alpha; 30 int i,j,k,w; 31 w=0; 32 for(i=1;i<=n;i++) 33 { 34 if(c[u][i]>0&&level[i]==level[u]+1) 35 { 36 if(k=findpath(i,Min(c[u][i],alpha-w))) 37 { 38 c[u][i]-=k; 39 c[i][u]+=k; 40 w+=k; 41 } 42 } 43 } 44 return w; 45 } 46 47 int dinic() 48 { 49 int i,j,k=0; 50 while(makelevel()) 51 while(i=findpath(s,Inf)) 52 k+=i; 53 return k; 54 }
dinic求最大流,图用静态邻接表表示。
1 #define Min(a,b) a<b?a:b 2 #define N 10000 3 #define Inf 9999999 4 struct Edge 5 { 6 int to,cap,opt,next; 7 }e[100000]; 8 int ec,pp[N],n,s,t; 9 int level[N],que[N]; 10 11 bool makelevel() 12 { 13 int i,j,k,head=0,tail=0; 14 memset(level,-1,sizeof(level)); 15 level[s]=0; 16 que[tail++]=s; 17 while(head<tail) 18 { 19 k=que[head++]; 20 for(i=pp[k];i!=-1;i=e[i].next) 21 if(e[i].cap>0&&level[e[i].to]==-1) 22 { 23 level[e[i].to]=level[k]+1; 24 que[tail++]=e[i].to; 25 } 26 } 27 return level[t]!=-1; 28 } 29 30 int findpath(int u,int alpha) 31 { 32 if(u==t) 33 return alpha; 34 int i,j,k,w=0; 35 for(i=pp[u];i!=-1;i=e[i].next) 36 if(e[i].cap>0&&level[e[i].to]==level[u]+1) 37 if(k=findpath(e[i].to,Min(e[i].cap,alpha-w))) 38 { 39 e[i].cap-=k; 40 e[e[i].opt].cap+=k; 41 w+=k;//多路增广 42 } 43 return w; 44 } 45 46 int dinic() 47 { 48 int i,j,k=0; 49 while(makelevel()) 50 while(i=findpath(s,Inf)) 51 k+=i; 52 return k; 53 }
sap算法求最大流,图用邻接矩阵表示。
1 #define N 10000 2 #define Inf 9999999 3 int G[N][N],n;//初始网络 4 int c[N][N];//残留网络 5 int dist[N],que[N],gap[2*N],pre[N]; 6 7 void bfs(int t)//从汇点bfs求得层次图 8 { 9 int i,j,k,head=0,tail=0; 10 memset(dist,0,sizeof(dist)); 11 memset(gap,0,sizeof(gap)); 12 gap[0]=1; 13 que[tail++]=t; 14 while(head<tail) 15 { 16 k=que[head++]; 17 for(i=1;i<=n;i++) 18 { 19 if(G[i][k]>0&&!dist[i]&&i!=t) 20 { 21 dist[i]=dist[k]+1; 22 gap[dist[i]]++; 23 que[tail++]=i; 24 } 25 } 26 } 27 } 28 29 int sap(int s,int t) 30 { 31 int i,j,k,flow=0,u=s,neck; 32 bfs(t); 33 while(dist[s]<n) 34 { 35 if(u==t) 36 { 37 int cc=Inf; 38 j=t;i=pre[j]; 39 while(j!=s) 40 { 41 if(c[i][j]<cc) 42 { 43 cc=c[i][j]; 44 neck=i; 45 } 46 j=i; 47 i=pre[j]; 48 } 49 flow+=cc; 50 j=t; 51 i=pre[j]; 52 while(j!=s) 53 { 54 c[i][j]-=cc; 55 c[j][i]+=cc; 56 j=i; 57 i=pre[j]; 58 } 59 u=neck; 60 } 61 for(i=1;i<=n;i++) 62 if(c[u][i]>0&&dist[u]==dist[i]+1) 63 break; 64 if(i<=n) 65 { 66 pre[i]=u; 67 u=i; 68 } 69 else 70 { 71 if(--gap[dist[u]]==0)break; 72 int tmp=n-1; 73 for(i=1;i<=n;i++) 74 if(c[u][i]>0&&dist[i]<tmp) 75 tmp=dist[i]; 76 dist[u]=tmp+1; 77 gap[dist[u]]++; 78 if(u!=s)u=pre[u]; 79 } 80 } 81 return flow; 82 }
sap算法求最大流,图用静态邻接表表示。
1 #define N 10000 2 #define Inf 99999999 3 struct Edge 4 { 5 int to,cap,opt,next; 6 }e[100000]; 7 int ec,pp[10000],n;//pp[i]用于保存第i个节点的边,n为图的节点数目 8 int que[N],dist[N],gap[2*N],pre[N],cur[N]; 9 10 void bfs(int t) 11 { 12 int i,j,k,head=0,tail=0; 13 memset(dist,0,sizeof(dist)); 14 memset(gap,0,sizeof(gap)); 15 gap[0]=1; 16 que[tail++]=t; 17 while(head<tail) 18 { 19 k=que[head++]; 20 for(i=pp[k];i!=-1;i=e[i].next) 21 { 22 int v=e[i].to; 23 if(!dist[v]&&v!=t) 24 { 25 dist[v]=dist[k]+1; 26 gap[dist[v]]++; 27 que[tail++]=v; 28 } 29 } 30 } 31 } 32 33 int sap(int s,int t) 34 { 35 int i,j,k,flow=0,u=s,neck; 36 memcpy(cur,pp,sizeof(pp)); 37 bfs(t); 38 while(dist[s]<n) 39 { 40 if(u==t) 41 { 42 int cc=Inf; 43 for(i=s;i!=t;i=e[cur[i]].to) 44 if(e[cur[i]].cap<cc) 45 { 46 neck=i; 47 cc=e[cur[i]].cap; 48 } 49 flow+=cc; 50 for(i=s;i!=t;i=e[cur[i]].to) 51 { 52 e[cur[i]].cap-=cc; 53 e[e[cur[i]].opt].cap+=cc; 54 } 55 u=neck; 56 } 57 for(i=cur[u];i!=-1;i=e[i].next) 58 if(e[i].cap>0&&dist[u]==dist[e[i].to]+1) 59 break; 60 if(i!=-1) 61 { 62 cur[u]=i; 63 pre[e[i].to]=u; 64 u=e[i].to; 65 } 66 else 67 { 68 if(--gap[dist[u]]==0)break; 69 cur[u]=pp[u]; 70 int tmp=n-1; 71 for(i=pp[u];i!=-1;i=e[i].next) 72 if(e[i].cap>0&&dist[e[i].to]<tmp) 73 tmp=dist[e[i].to]; 74 dist[u]=tmp+1; 75 gap[dist[u]]++; 76 if(u!=s)u=pre[u]; 77 } 78 } 79 return flow; 80 }
===============================================================================================================================================================================================================================================================================================================
题目:
最大流
POJ 1273 Drainage Ditches
POJ 1274 The Perfect Stall (二分图匹配)
POJ 1698 Alice's Chance
POJ 1459 Power Network
POJ 2112 Optimal Milking (二分)
POJ 2455 Secret Milking Machine (二分)
POJ 3189 Steady Cow Assignment (枚举)
POJ 1637 Sightseeing tour (混合图欧拉回路)
POJ 3498 March of the Penguins (枚举汇点)
POJ 1087 A Plug for UNIX
POJ 1149 Pigs (构图题)
ZOJ 2760 How Many Shortest Path (边不相交最短路的条数)
POJ 2391 Ombrophobic Bovines (必须拆点,否则有BUG)
WHU 1124 Football Coach (构图题)
SGU 326 Perspective (构图题,类似于 WHU 1124)
UVa 563 Crimewave
UVa 820 Internet Bandwidth
POJ 3281 Dining (构图题)
POJ 3436 ACM Computer Factory
POJ 2289 Jamie's Contact Groups (二分)
SGU 438 The Glorious Karlutka River =) (按时间拆点)
SGU 242 Student's Morning (输出一组解)
SGU 185 Two shortest (Dijkstra 预处理,两次增广,必须用邻接阵实现,否则 MLE)
HOJ 2816 Power Line
POJ 2699 The Maximum Number of Strong Kings (枚举+构图)
ZOJ 2332 Gems
JOJ 2453 Candy (构图题)
SOJ3312 Stockholm Knights
SOJ3353 Total Flow
SOJ2414 Leapin' Lizards
最小割
SOJ3106 Dual Core CPU
SOJ3109 Space flight
SOJ3107 Select
SOJ3185 Black and white
SOJ3254 Rain and Fgj
SOJ3134 windy和水星 -- 水星交通
HOJ 2634 How to earn more
ZOJ 2071 Technology Trader (找割边)
HNU 10940 Coconuts
ZOJ 2532 Internship (找关键割边)
POJ 1815 Friendship (字典序最小的点割集)
POJ 3204 Ikki's Story I - Road Reconstruction (找关键割边)
POJ 3308 Paratroopers
POJ 3084 Panic Room
POJ 3469 Dual Core CPU
ZOJ 2587 Unique Attack (最小割的唯一性判定)
POJ 2125 Destroying The Graph (找割边)
ZOJ 2539 Energy Minimization
TJU 2944 Mussy Paper (最大权闭合子图)
POJ 1966 Cable TV Network (无向图点连通度)
HDU 1565 方格取数(1) (最大点权独立集)
HDU 1569 方格取数(2) (最大点权独立集)
POJ 2987 Firing (最大权闭合子图)
SPOJ 839 Optimal Marks (将异或操作转化为对每一位求最小割)
HOJ 2811 Earthquake Damage (最小点割集)
2008 Beijing Regional Contest Problem A Destroying the bus stations ( BFS 预处理 )(http://acmicpc-live-archive.uva.es/nuevoportal/data/problem.php?p=4322)
ZOJ 2676 Network Wars (参数搜索)
POJ 3155 Hard Life (参数搜索)
ZOJ 3241 Being a Hero
有上下界
ZOJ 2314 Reactor Cooling (无源汇可行流)
POJ 2396 Budget (有源汇可行流)
SGU 176 Flow Construction (有源汇最小流)
ZOJ 3229 Shoot the Bullet (有源汇最大流)
HDU 3157 Crazy Circuits (有源汇最小流)
最小费用流
HOJ 2715 Matrix3
HOJ 2739 The Chinese Postman Problem
POJ 2175 Evacuation Plan (消一次负圈)
POJ 3422 Kaka's Matrix Travels (与 Matrix3 类似)
POJ 2516 Minimum Cost (按物品种类多次建图)
POJ 2195 Going Home
BUAA 1032 Destroying a Painting
POJ 2400 Supervisor, Supervisee (输出所有最小权匹配)
POJ 3680 Intervals
HOJ 2543 Stone IV
POJ 2135 Farm Tour
BASHU2445 餐巾问题
---------------------------------------------onmylove原创
zju1992:http://acm.zju.edu.cn/show_problem.php?pid=1992
Uva563, http://icpcres.ecs.baylor.edu/onlinejudge/
点不交的路径条数问题,需要拆点
SGU 176 http://acm.sgu.ru/problem.php?contest=0&problem=176
容量有上下界的网络流问题,有难度
Spoj660:http://www.spoj.pl/problems/QUEST4/
Spoj377:http://www.spoj.pl/problems/TAXI/
UVA
http://icpcres.ecs.baylor.edu/onlinejudge/
753,
820,
10122,
10330,
10511,
10735.
SGU 176 http://acm.sgu.ru/problem.php?contest=0&problem=176
容量有上下界的网络流问题,有难度
Spoj660:http://www.spoj.pl/problems/QUEST4/
Spoj377:http://www.spoj.pl/problems/TAXI/
UVA
http://icpcres.ecs.baylor.edu/onlinejudge/
753,
820,
10122,
10330,
10511,
10735.
最大流题目:
TC:
Single Round Match 200 Round 1 – Division I, Level Three
Single Round Match 236 Round 1 – Division I, Level Three
Single Round Match 399 Round 1 – Division I, Level Three
同Hoj1024: http://acm.hust.edu.cn/thx/problem.php?id=1024
2003 TCO Semifinal Round 4 – Division I, Level Three
2004 TCCC Championship Round – Division I, Level Three
2005 TCO Sponsor Track Round 3 – Division I, Level One
混合图的欧拉回路
Poj1637: http://acm.pku.edu.cn/JudgeOnline/problem?id=1637
求增广边:
Poj3204:http://acm.pku.edu.cn/JudgeOnline/problem?id=3204
类似:Hoj1082: http://acm.hust.edu.cn/thx/problem.php?cid=1017&pid=6
项目选择问题:
Poj3469:http://acm.pku.edu.cn/JudgeOnline/problem?id=3469
Zoj2930:http://acm.zju.edu.cn/show_problem.php?pid=2930
求项目选择项目最多的方案。
建图:
Poj1149:http://acm.pku.edu.cn/JudgeOnline/problem?id=1149
Poj3436:http://acm.pku.edu.cn/JudgeOnline/problem?id=3436
Poj3281:http://acm.pku.edu.cn/JudgeOnline/problem?id=3281
连通度:
点连通度Poj1966: http://acm.pku.edu.cn/JudgeOnline/problem?id=1966
最小割:
Poj2914:http://acm.pku.edu.cn/JudgeOnline/problem?id=2914
(stoer-Wagner)
基本题:
Poj3498:http://acm.pku.edu.cn/JudgeOnline/problem?id=3498
枚举:做n次最大流。
Poj1087:http://acm.pku.edu.cn/JudgeOnline/problem?id=1087
可以用最大流做,也可以用二分图匹配做。
Poj1273:http://acm.pku.edu.cn/JudgeOnline/problem?id=1273
Poj1274:http://acm.pku.edu.cn/JudgeOnline/problem?id=1274
Poj1325: http://acm.pku.edu.cn/JudgeOnline/problem?id=1325
poj1459:http://acm.pku.edu.cn/JudgeOnline/problem?id=1459
Poj1797:http://acm.pku.edu.cn/JudgeOnline/problem?id=1797
Poj1815:http://acm.pku.edu.cn/JudgeOnline/problem?id=1815
poj2112:http://acm.pku.edu.cn/JudgeOnline/problem?id=2112
poj2239:http://acm.pku.edu.cn/JudgeOnline/problem?id=2239
poj2289: http://acm.pku.edu.cn/JudgeOnline/problem?id=2289
Poj2391:http://acm.pku.edu.cn/JudgeOnline/problem?id=2391
Poj2987:http://acm.pku.edu.cn/JudgeOnline/problem?id=2987
Poj3308:http://acm.pku.edu.cn/JudgeOnline/problem?id=3308
提示:最大权闭包,转化成最大流
Poj3155: http://acm.pku.edu.cn/JudgeOnline/problem?id=3155
难
SGU 176 http://acm.sgu.ru/problem.php?contest=0&problem=176
容量有上下界的网络流问题,有难度
Spoj660:http://www.spoj.pl/problems/QUEST4/
Spoj377:http://www.spoj.pl/problems/TAXI/
http://icpcres.ecs.baylor.edu/onlinejudge/
820,
10122,
10330,
10511,
10735.