网络流讲义.docx
《网络流讲义.docx》由会员分享,可在线阅读,更多相关《网络流讲义.docx(10页珍藏版)》请在冰豆网上搜索。
网络流讲义
网络流算法及其应用
5.1基本概念
在实际生活中有许多流量问题,例如在交通运输网络中的人流、车流、货物流,供水网络中的水流,金融系统中的现金流,通讯系统中的信息流,等等。
50年代以福特(Ford)、富克逊(Fulkerson)为代表建立的“网络流理论”,是网络应用的重要组成部分。
在最近的奥林匹克信息学竞赛中,利用网络流算法高效地解决问题已不是什么稀罕的事了。
本节着重介绍最大流(包括最小费用)算法,并通过实际例子,讨论如何在问题的原型上建立—个网络流模型,然后用最大流算法高效地解决问题。
1.问题描述如图5-1所示是联结某产品地v1和销售地v4的交通网,每一弧(vi,vj)代表从vi到vj的运输线,产品经这条弧由vi输送到vj,弧旁的数表示这条运输线的最大通过能力。
产品经过交通网从v1到v4。
现在要求制定一个运输方案使从v1到v4的产品数量最多。
图5-1图5-2
2.网络与网络流
给一个有向图N=(V,E),在V中指定一点,称为源点(记为vs,和另一点,称为汇点(记为vt),其余的点叫中间点,对于E中每条弧(vi,vj)都对应一个正整数c(vi,vj)≥O(或简写成cij),称为f的容量,则赋权有向图N=(V,E,c,vs,vt)称为一个网络。
如图5-1所给出的一个赋权有向图N就是一个网络,指定v1是源点,v4为汇点,弧旁的数字为cij。
所谓网络上的流,是指定义在弧集合E上一个函数f={f(vi,vj)},并称f(vi,vj)为弧(vi,vj)上的流量(下面简记为fij)。
如图5-2所示的网络N,弧上两个数,第一个数表示容量cij,第二个数表示流量fij。
3.可行流与最大流
在运输网络的实际问题中,我们可以看出,对于流有两个显然的要求:
一是每个弧上的流量不能超过该弧的最大通过能力(即弧的容量);二是中间点的流量为0,源点的净流出量和汇点的净流入量必相等且为这个方案的总输送量。
因此有:
(1)容量约束:
0≤fij≤cij,(vi,vj)∈E,
(2)守恒条件
对于中间点:
流入量=流出量;对于源点与汇点:
源点的净流出量vs(f)=汇点的净流入量(-vt(f))
的流f,称为网络N上的可行流,并将源点s的净流量称为流f的流值v(f)。
网络N中流值最大的流f*称为N的最大流。
4.可增广路径
所谓可增广路径,是指这条路径上的流可以修改,通过修改,使得整个网络的流值增大。
设f是一个可行流,P是从源点s到汇点t的一条路,若p满足下列条件:
(1)在p上的所有前向弧(vi→vj)都是非饱和弧,即0≤fij(2)在p上的所有后向弧(vi←vj)都是非零弧,即0则称p为(关于可行流f的)一条可增广路径。
5.最大流定理
当且仅当不存在关于f*的增广路径,可行流f*为最大流。
5.2最大流算法
算法思想:
最大流问题实际上是求一可行流{fij},使得v(f达到最大。
若给了一个可行流f,只要判断N中有无关于f的增广路径,如果有增广路径,改进f,得到一个流量增大的新的可行流;如果没有增广路径,则得到最大流。
1.寻求最大流的标号法(Ford,Fulkerson)
从一个可行流(一般取零流)开始,不断进行以下的标号过程与调整过程,直到找不到关于f的可增广路径为止。
(1)标号过程
在这个过程中,网络中的点分为已标号点和未标号点,已标号点又分为已检查和未检查两种。
每个标号点的标号信息表示两个部分:
第一标号表明它的标号从哪一点得到的,以便从vt开始反向追踪找出也增广路径;第二标号是为了表示该顶点是否已检查过。
标号开始时,给vs标上(s,0),这时vs是标号但末检查的点,其余都是未标号的点,记为(0,0)。
取一个标号而未检查的点vi,对于一切未标号的点vj:
A.对于弧(vi,vj),若fijB.对于弧(vi,vj),若fji>0,则给vj标号(-vi,0),这时,vj点成为标号而未检查的点。
于是vi成为标号且已检查的点,将它的第二个标号记为1。
重复上述步骤,一旦vt被标上号,表明得到一条从vi到vt的增广路径p,转入调整过程。
若所有标号都已检查过去,而标号过程进行不下去时,则算法结束,这时的可行流就是最大流。
(2)调整过程
从vt点开始,通过每个点的第一个标号,反向追踪,可找出增广路径P。
例如设vt的第一标号为vk(或-vk),则弧(vk,vt)(或相应地(vt,vk))是p上弧。
接下来检查vk的第一标号,若为vi(或-vi),则找到(vi,vk)(或相应地(vk,vi))。
再检查vi的第一标号,依此类推,直到vs为止。
这时整个增广路径就找到了。
在上述找增广路径的同时计算Q:
Q=min{min(cij-fij),minf*ij}
对流f进行如下的修改:
f'ij=fij+Q(vi,vj)∈P的前向弧的集合
f'ij=fij-Q(vi,vj)∈P的后向弧的集合
f'ij=f*ij(vi,vj)不属于P的集合
接着,清除所有标号,对新的可行流f’,重新进入标号过程。
例1:
下图表示一个公路网,V1是某原材料产地,V6表示港口码头,每段路的通过能力(容量)如图上的各边上的数据,找一运输方案,使运输到码头的原材料最多?
程序如下:
ProgramMax_Stream;
ConstMaxn=20;
type
nettype=record
C,F:
integer;
end;
nodetype=record
L,P:
integer;
end;
var
Lt:
array[0..maxn]ofnodetype;
G:
Array[0..Maxn,0..Maxn]ofNettype;
N,S,T:
integer;
F:
Text;
ProcedureInit;{初始化过程,读人有向图,并设置流为0}
VarFn:
String;
I,J:
Integer;
Begin
Write('GraphFile=');Readln(Fn);
Assign(F,Fn);
Reset(F);
Readln(F,N);
Fillchar(G,Sizeof(G),0);
Fillchar(Lt,Sizeof(Lt),0);
ForI:
=1ToNDo
ForJ:
=1ToNDoRead(F,G[I,J].C);
Close(F);
End;
FunctionFind:
Integer;{寻找已经标号未检查的顶点}
VarI:
Integer;
Begin
I:
=1;
While(I<=N)AndNot((Lt[I].L<>0)And(Lt[I].P=0))DoInc(I);
IfI>NThenFind:
=0ElseFind:
=I;
End;
FunctionFord(VarA:
Integer):
Boolean;
Var{用标号法找增广路径,并求修改量A}
I,J,M,X:
Integer;
Begin
Ford:
=True;
Fillchar(Lt,Sizeof(Lt),0);
Lt[S].L:
=S;
Repeat
I:
=Find;
Ifi=0ThenExit;
ForJ:
=1ToNDo
If(Lt[J].L=0)And((G[I,J].C<>0)or(G[J,I].C<>0))Then
Begin
if(G[I,J].F=I;
If(G[J,I].F>0)ThenLt[J].L:
=-I;
End;
Lt[I].P:
=1;
Until(Lt[T].L<>0);
M:
=T;A:
=Maxint;
Repeat
J:
=M;M:
=Abs(Lt[J].L);
IfLt[J].L<0ThenX:
=G[J,M].F;
IfLt[J].L>0ThenX:
=G[M,J].C-G[M,J].F;
IfX=X;
UntilM=S;
Ford:
=False;
End;
ProcedureChange(A:
Integer);{调整过程}
VarM,J:
Integer;
Begin
M:
=T;
Repeat
J:
=M;M:
=Abs(Lt[J].L);
IfLt[J].L<0ThenG[J,M].F:
=G[J,M].F-A;
IfLt[J].L>0ThenG[M,J].F:
=G[M,j].F+A;
UntilM=S;
End;
ProcedurePrint;{打印最大流及其方案}
VAR
I,J:
Integer;
Max:
integer;
Begin
Max:
=0;
ForI:
=1ToNDO
Begin
IfG[I,T].F<>0ThenMax:
=Max+G[I,T].F;
ForJ:
=1ToNDo
IfG[I,J].F<>0ThenWriteln(I,'->',J,'',G[I,J].F);
End;
Writeln('TheMaxStream=',Max);
End;
ProcedureProcess;{求最大流的主过程}
VarDel:
Integer;
Success:
Boolean;
Begin
S:
=1;T:
=N;
Repeat
Success:
=Ford(Del);
IfSuccessThenPrint
ElseChange(Del);
UntilSuccess;
End;
Begin{MainProgram}
Init;
Process;
End.
测试数据文件(zdl.txt)如下:
6
035000
001400
000020
000005
010302
000000
运行结果如下:
GraphFile=zdl.txt
1->23
1->32
2->43
3->52
4->63
5->62
TheMaxStream=5
5.3最小费用最大流及算法
上面的网络,可看作为辅送一般货物的运输网络,此时,最大流问题仅表明运输网络运输货物的能力,但没有考虑运送货物的费用。
在实际问题中,运送同样数量货物的运输方案可能有多个,因此从中找一个输出费用最小的的方案是一个很重要的问题,这就是最小代价流所要讨论的内容。
1.最小费用最大流问题的模型
给定网络N=(V,E,c,w,s,t),每一弧(vi,vj)属于E上,除了已给容量cij外,还给了一个单位流量的费用w(vi,vj)≥O(简记为wij)。
所谓最小费用最大流问题就是要求一个最大流f,使得流的总输送费用取最小值。
W(f)=∑wijfij
2.用对偶法解最小费用最大流问题
定义:
已知网络N=(V,E,c,w,s,t),f是N上的一个可行流,p为vs到vt(关于流f)的可增广路径,称W(p)=∑wij(p+)-∑wij(p-)为路径p的费用。
若p*是从vs到vt所有可增广路径中费用最小的路径,则称p*为最小费用可增广路径。
定理:
若f是流量为v(f)的最小费用流,p是关于f的从vs到vt的一条最小费用可增广路径,则f经过p调整流量Q得到新的可行流f’(记为f’=f+Q),一定是流量为v(f)+Q的可行流中的最小费用流。
3.对偶法的基本思路
①取f={0}
②寻找从vs到vt的一条最小费用可增广路径p。
若不存在p,则f为N中的最小费用最大流,算法结束。
若存在p,则用求最大流的方法将f调整成f*,使v(f*)=v(f)+Q,并将f*赋值给f,转②。
4.迭代法求最小费用可增广路径
在前一节中,我们已经知道了最大流的求法。
在最小费用最大流的求解中,每次要找一条最小费用的增广路径,这也是与最大流求法唯一不同之处。
于是,对于求最小费用最大流问题余下的问题是怎样寻求关于f的最小费用增广路径p。
为此,我们构造一个赋权有向图b(f),它的顶点是原网络N中的顶点,而把N中每一条弧(vi,vj)变成两个相反方向的弧(vi,vj)和(vj,vi)。
定义w(f)中的弧的权如下:
如果(fij如果(fij>0),则bji=-wij;如果(fij=cij),则bji=+oo
于是在网络N中找关于f的最小费用增广路径就等价于在赋权有向图b(f)中,寻求从vs到vt的最短路径。
求最短路有三种经典算法,它们分别是Dijkstra算法、Floyd算法和迭代算法(ford算法)。
由于在本问题中,赋权有向图b(f)中存在负权,故我们只能用后两种方法求最短路,其中对于本问题最高效的算法是迭代算法。
为了程序的实现方便,我们只要对原网络做适当的调整。
将原网络中的每条弧(vi,vj)变成两条相反的弧:
前向弧(vi,vj),其容量cij和费用wij不变,流量为fij;
后向弧(vj,vi),其容量0和费用-wij,流量为-fij。
事实上,对于原网络的数据结构中,这些单元已存在,在用标号法求最大流时是闲置的。
这样我们就不必产生关于流f的有向图b(f)。
同时对判断(vi,vj)的流量可否改时,可以统一为判断是否“fij因为对于后向弧(vj,vi),若fij>0,则fji=-fij<0=cji。
例2:
求输送量最大且费用最小的运输方案?
如下图是一公路网,v1是仓库所在地(物资的起点),v5是某一工地(物资的终点)每条弧旁的两个数字分别表示某一时间内通过该段路的最多吨数和每吨物资通过该段路的费用。
程序如下:
{最小费用最大流参考程序}{效率不高}
programMaxflow_With_MinCost;
const
name1='flow.in';
name2='flow.out';
maxN=100;{最多顶点数}
type
Tbest=record{数组的结构}
value,fa:
integer;
end;{到源点的最短距离,父辈}
Nettype=record
{网络邻接矩阵类型}
c,w,f:
integer;
{弧的容量,单位通过量费用,流量}
end;
var
Net:
array[1..maxN,1..maxN]OfNettype;
{网络N的邻接矩阵}
n,s,t:
integer;{顶点数,源点、汇点的编号}
Minw:
integer;{最小总费用}
best:
array[1..maxN]ofTbest;{求最短路时用的数组}
procedureInit;{数据读人}
varinf:
text;
a,b,c,d:
integer;
begin
fillchar(Net,sizeof(Net),0);
Minw:
=0;
assign(inf,name1);
reset(inf);
readln(inf,n);
s:
=1;t:
=n;{源点为1,汇点n}
repeat
readln(inf,a,b,c,d);
ifa+b+c+d>0then
begin
Net[a,b].c:
=c;{给弧(a,b)赋容量c}
Net[b,a].c:
=0;{给相反弧(b,a)赋容量0}
Net[a,b].w:
=d;{给弧(a,b)赋费用d}
Net[b,a].w:
=-d;{给相反孤(b,a)赋费用-d}
end;
untila+b+c+d=0;
close(inf);
end;
functionFind_Path:
boolean;
{求最小费用增广路函数,若best[t].value<>MaxInt,
则找到增广路,函数返回true,否则返回false}
varQuit:
Boolean;
i,j:
Integer;
begin
fori:
=1tondobest[i].value:
=Maxint;best[s].value:
=0;
{寻求最小费用增广路径前,给best数组赋初值}
repeat
Quit:
=True;
fori:
=1tondo
ifbest[i].Valueforj:
=1tondo
if(Net[i,j].f(best[i].value+Net[i,j].w<
best[j].value)then
begin
best[j].value:
=best[i].value+Net[i,j].w;
best[j].fa:
=i;
Quit:
=False;
end;
untilQuit;
ifbest[t].value=trueelsefind_path:
=false;
end;
procedureAdd_Path;
vari,j:
integer;
begin
i:
=t;
whilei<>sdo
begin
j:
=best[i].fa;
inc(Net[j,i].f);{增广路是弧的数量修改,增量1}
Net[i,j].f:
=-Net[j,i].f;{给对应相反弧的流量赋值-f}
i:
=j;
end;
inc(Minw,best[t].value);{修改最小总费用的值}
end;
procedureOut;{输出最小费用最大流的费用及方案}
varouf:
text;
i,j:
integer;
begin
assign(ouf,name2);
rewrite(ouf);
writeln(ouf,'Minw=',Minw);
fori:
=1tondo
forj:
=1tondo
ifNet[i,j].f>0then
writeln(ouf,i,'->',j,':
',
Net[i,j].w,'*',Net[i,j].f);
close(ouf);
end;
Begin{主程序}
Init;
whileFind_PathdoAdd_Path;
{当找到最小费用增广路,修改流}
Out;
end.
flow.in如下:
5
12104
1381
2426
2571
3252
34103
4542
0000
运行结果flow.out如下:
Minw=55
1->2:
4*3
1->3:
1*8
2->5:
1*7
3->2:
2*4
3->4:
3*4
4->5:
2*4
练习:
1.熟记上述两种算法。
2.将例2程序中的寻找费用最小增广路径的算法改成Floyd算法。