最大流的网络,可看作为辅送一般货物的运输网络,此时,最大流问题仅表明运输网络运输货物的能力,但没有考虑运送货物的费用。在实际问题中,运送同样数量货物的运输方案可能有多个,因此从中找一个输出费用最小的的方案是一个很重要的问题,这就是最小代价流所要讨论的内容。
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<cij),则bij=wij
;如果(fij=cij),则bij=+oo
如果(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<cij”。因为对于后向弧(vj,vi),若fij>0,则fji=-fij<0=cji。
例2:求输送量最大且费用最小的运输方案?
如下图是一公路网,v1是仓库所在地(物资的起点),v5是某一工地(物资的终点)每条弧旁的两个数字分别表示某一时间内通过该段路的最多吨数和每吨物资通过该段路的费用。
程序如下:
{最小费用最大流参考程序}
program Maxflow_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] Of
Nettype;
{网络N的邻接矩阵}
n,s,t:integer;{顶点数,源点、汇点的编号}
Minw:integer;{最小总费用}
best:array[1..maxN] of Tbest;{求最短路时用的数组}
procedure Init;{数据读人}
var inf: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);
if
a+b+c+d>0 then
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;
until
a+b+c+d=0;
close(inf);
end;
function
Find_Path:boolean;
{求最小费用增广路函数,若best[t].value<>MaxInt,
则找到增广路,函数返回true,否则返回false}
var
Quit:Boolean;
i,j:Integer;
begin
for i:=1 to
n do best[i].value:=Maxint;best[s].value:=0;
{寻求最小费用增广路径前,给best数组赋初值}
repeat
Quit:=True;
for i:=1 to n
do
if best[i].Value < Maxint
then
for j:=1 to n
do
if (Net[i,j].f <
Net[i,j].c) and
(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;
until Quit;
if best[t].value<Maxint then find_path:=true else
find_path:=false;
end;
procedure Add_Path;
var i,j: integer;
begin
i:= t;
while i <> s do
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;
procedure Out;{输出最小费用最大流的费用及方案}
var ouf: text;
i,j:
integer;
begin
assign(ouf,name2);
rewrite(ouf);
writeln(ouf,‘Min w = ‘,Minw);
for i := 1 to n do
for j:= 1 to n do
if Net[i,j].f > 0
then
writeln(ouf,i, ‘ -> ‘,j,‘:
‘,
Net[i,j].w,‘*‘,Net[i,j].f);
close(ouf);
end;
Begin {主程序}
Init;
while Find_Path do
Add_Path;
{当找到最小费用增广路,修改流}
Out;
end.
flow.in如下:
5
1 2 10 4
1 3 8 1
2 4 2 6
2 5 7
1
3 2 5 2
3 4 10 3
4 5 4 2
0 0 0 0
运行结果flow.out如下:
Min w = 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
源自:OIBH