1、augment path,直譯為“增廣路”,其思想大致如下:
原有網絡為G,設有一輔助圖G',其定義為V(G') = V(G),E(G')初始值(也就是容量)與E(G)相同。每次操作時從Source點搜索出一條到Sink點的路徑,然後將該路徑上所有的容量減去該路徑上容量的最小值,然後對路徑上每一條邊<u,v>添加或擴大反方向的容量,大小就是剛纔減去的容量。一直到沒有路為止。此時輔助圖上的正嚮流就是最大流。
我們很容易覺得這個算法會陷入死循環,但事實上不是這樣的。我們衹需要註意到每次網絡中由Source到Sink的流都增加了,若容量都是整數,則這個算法必然會結束。
尋找通路的時候可以用DFS,BFS最短路等算法。就這兩者來說,BFS要比DFS快得多,但是編碼量也會相應上一個數量級。
增廣路方法可以解决最大流問題,然而它有一個不可避免的缺陷,就是在極端情況下每次衹能將流擴大1(假設容量、流為整數),這樣會造成性能上的很大問題,解决這個問題有一個復雜得多的算法,就是預推進算法。
2、push label,直譯為“預推進”算法。
3、壓入與重標記(Push-Relabel)算法
除了用各種方法在剩餘網絡中不斷找增廣路(augmenting)的Ford-Fulkerson係的算法外,還有一種求最大流的算法被稱為壓入與重標記(Push-Relabel)算法。它的基本操作有:壓入,作用於一條邊,將邊的始點的預流盡可能多的壓嚮終點;重標記,作用於一個點,將它的高度(也就是label)設為所有鄰接點的高度的最小值加一。Push-Relabel係的算法普遍要比Ford-Fulkerson係的算法快,但是缺點是相對難以理解。
Relabel-to-Front使用一個鏈表保存溢出頂點,用Discharge操作不斷使溢出頂點不再溢出。Discharge的操作過程是:若找不到可被壓入的臨邊,則重標記,否則對臨邊壓入,直至點不再溢出。算法的主過程是:首先將源點出發的所有邊充滿,然後將除源和匯外的所有頂點保存在一個鏈表裏,從鏈表頭開始進行Discharge,如果完成後頂點的高度有所增加,則將這個頂點置於鏈表的頭部,對下一個頂點開始Discharge。
Relabel-to-Front算法的時間復雜度是O(V^3),還有一個叫Highest Label Preflow Push的算法復雜度據說是O(V^2*E^0.5)。我研究了一下HLPP,感覺它和Relabel-to-Front本質上沒有區別,因為Relabel-to-Front每次前移的都是高度最高的頂點,所以也相當於每次選擇最高的標號進行更新。還有一個感覺也會很好實現的算法是使用隊列維護溢出頂點,每次對pop出來的頂點discharge,出現了新的溢出頂點時入隊。
Push-Relabel類的算法有一個名為gap heuristic的優化,就是當存在一個整數0<k<V,沒有任何頂點滿足h[v]=k時,對所有h[v]>k的頂點v做更新,若它小於V+1就置為V+1。
cpp程序:
#include<cstdio>
const long maxn=1000+10;
const long maxm=440000+100;
const long maxnum=100000000;
long n,m,s=0,tot=0,list[maxn],p[maxn],g[maxn],gg[maxn],dis[maxn];
bool mark[maxn];
struct node
{
long k,f,c,next,g;
}d[maxm<<1];
void insert(long u,long v,long c)
{
d[tot].k=v;
d[tot].f=0;
d[tot].c=c;
d[tot].next=g;
g=tot;
d[tot].g=tot+1;
tot++;
d[tot].k=u;
d[tot].f=0;
d[tot].c=0;
d[tot].next=g[v];
g[v]=tot;
d[tot].g=tot-1;
tot++;
}
void bfs()
{
long x,u,v,h,t;
for (long i=0;i<n;i++)
dis=0;
h=0;
t=1;
list=0;
最小費用最大流
一.Ford和Fulkerson迭加算法.
基本思路:把各條弧上單位流量的費用看成某種長度,用求解最短路問題的方法確定一條自V1至Vn的最短路;在將這條最短路作為可擴充路,用求解最大流問題的方法將其上的流量增至最大可能值;而這條最短路上的流量增加後,其上各條弧的單位流量的費用要重新確定,如此多次迭代,最終得到最小費用最大流.
迭加算法:
1) 給定目標流量F或∞,給定最小費用的初始可行流=0
2) 若V(f)=F,停止,f為最小費用流;否則轉(3).
3) 構造 相應的新的費用有嚮圖W(fij),在W(fij)尋找Vs到Vt的最小費用有嚮路P(最短路),沿P增加流f的流量直到F,轉(2);若不存在從Vs到Vt的最小費用的有嚮路P,停止.f就是最小費用最大流.
具體解題步驟:
設圖中雙綫所示路徑為最短路徑,費用有嚮圖為W(fij).
在圖(a)中給出零流 f,在圖(b)中找到最小費用有嚮路,修改圖(a)中的可行流,δ=min{4,3,5}=3,得圖(c),再做出(c)的調整容量圖,再構造相應的新的最小費用有嚮路得圖(d), 修改圖(c)中的可行流, δ=min{1,1,2,2}=1,得圖(e),以此類推,一直得到圖(h),在圖(h)中以無最小費用有嚮路,停止,經計算:
圖(h)中 最小費用=1*4+3*3+2*4+4*1+1*1+4*2+1*1+3*1=38
圖(g)中 最大流=5
最大流問題僅註意網絡流的流通能力,沒有考慮流通的費用。實際上費用因素是很重要的。例如在交通運輸問題中,往往要求在完成運輸任務的前提下,尋求一個使總運輸費用最省的運輸方案,這就是最小費用流問題。如果衹考慮單位貨物的運輸費用,那麽這個問題就變成最短路問題。由此可見,最短路問題是最小費用流問題的基礎。現已有一係列求最短路的成功方法。最小費用流(或最小費用最大流)問題 ,可以交替使用求解最大流和最短路兩種方法,通過迭代得到解决。
二.圈算法:
1) 利用Ford和Fulkson標號算法找出流量為F(<=最大流)的流f.
2) 構造f對應的調整容量的流網絡N'(f).
3) 搜索N'(f)中的負費用有嚮圖C(Floyd算法),若沒有則停止,否則轉(4).
4) 在C上找出最大的循環流,並加到N上去,同時修改N'(F)中C的容量,轉(3).
具體解題步驟:
利用Ford和Fulkson標號算法,得網絡的最大流F=5,見圖(a),由圖(a)構造相應的調整容量的流網絡(b),圖(b)中不存在負費用有嚮圖,故停止.經計算:
圖(b)中 最小費用= 4*1+3*1+1*1+3*3+4*2+1*1+4*1+2*4=38
圖(a)中 最大流為F=5
type
szbest=record
v,l:integer;
end;
szmap=record
c,f,w:integer;
end;
var
map:array[0..1000,0..1000] of szmap;
best:array[0..1000] of szbest;
i,j,ans,n,k,a,b,c,d,s,t:integer;
function find:boolean;
var i,j:integer;
flag:boolean;
begin
for i:=1 to n do best.v:=maxint;
best[s].v:=0;
repeat
flag:=true;
for i:=1 to n do
if best.v<maxint then
for j:=1 to n do
if (map[i,j].f<map[i,j].c)and(best.v+map
[i,j].w<best[j].v) then
begin
best[j].v:=best.v+map[i,j].w;
best[j].l:=i; flag:=false;
end;
until flag;
if best[t].v<maxint then exit(true) else exit(false);
end;
procedure updata;
var i,j:integer;
begin
i:=t;
while i<>s do begin
j:=best.l;
inc(map[j,i].f);
map[i,j].f:=-map[j,i].f;
i:=j;
end;
inc(ans,best[t].v);
end;
begin
assign(input,'g4.in');reset(input);
readln(n);
repeat
readln(a,b,c,d);
if a+b+c+d>0 then begin
map[a,b].c:=c; map[b,a].c:=0;
map[a,b].w:=d; map[b,a].w:=-d;
end;
until a+b+c+d=0;
s:=1; t:=n; ans:=0;
while find do updata;
writeln(ans);
for i:=1 to n do
for j:=1 to n do
if map[i,j].f>0 then writeln(i,' ',j,' ',map[i,j].w,'*',map
[i,j].f);
end.