本文主要講解最大流問題的Ford-Fulkerson解法。可以說這是一種方法,而不是算法,因為它包含具有不同運行時間的幾種實現。該方法依賴於三種重要思想:殘留網絡,增廣路徑和割。
在介紹着三種概念之前,我們先簡單介紹下Ford-Fulkerson方法的基本思想。首先需要了解的是Ford-Fulkerson是一種迭代的方法。開始時,對所有的u,v屬於V,f(u,v)=0(這里f(u,v)代表u到v的邊當前流量),即初始狀態時流的值為0。在每次迭代中,可以通過尋找一個“增廣路徑”來增加流值。增廣路徑可以看做是從源點s到匯點t之間的一條路徑,沿該路徑可以壓入更多的流,從而增加流的值。反復進行這一過程,直到增廣路徑都被找出為止。
舉個例子來說明下,如圖所示,每條紅線就代表了一條增廣路徑,當前s到t的流量為3。
當然這並不是該網絡的最大流,根據尋找增廣路徑的算法我們其實還可以繼續尋找增廣路徑,最終的最大流網絡如下圖所示,最大流為4。
接下來我們就介紹如何尋找增廣路徑。在介紹增廣路徑之前,我們首先需要介紹殘留網絡的概念。
殘留網絡
顧名思義,殘留網絡是指給定網絡和一個流,其對應還可以容納的流組成的網絡。具體說來,就是假定一個網絡G=(V,E),其源點s,匯點t。設f為G中的一個流,對應頂點u到頂點v的流。在不超過C(u,v)的條件下(C代表邊容量),從u到v之間可以壓入的額外網絡流量,就是邊(u,v)的殘余容量(residual capacity),定義如下:
r(u,v)=c(u,v)-f(u,v)
舉個例子,假設(u,v)當前流量為3/4,那么就是說c(u,v)=4,f(u,v)=3,那么r(u,v)=1。
我們知道,在網絡流中還有這么一條規律。從u到v已經有了3個單位流量,那么從反方向上看,也就是從v到u就有了3個單位的殘留網絡,這時r(v,u)=3。可以這樣理解,從u到v有3個單位流量,那么從v到u就有了將這3個單位流量的壓回去的能力。
我們來具體看一個例子,如下圖所示一個流網絡
其對應的殘留網絡為:
增廣路徑
在了解了殘留網絡后,我們來介紹增廣路徑。已知一個流網絡G和流f,增廣路徑p是其殘留網絡Gf中從s到t的一條簡單路徑。形象的理解為從s到t存在一條不違反邊容量的路徑,向這條路徑壓入流量,可以增加整個網絡的流值。上面的殘留網絡中,存在這樣一條增廣路徑:
其可以壓入4個單位的流量,壓入后,我們得到一個新的流網絡,其流量比原來的流網絡要多4。這時我們繼續在新的流網絡上用同樣的方法尋找增廣路徑,直到找不到為止。這時我們就得到了一個最大的網絡流。
流網絡的割
上面僅僅是介紹了方法,可是怎么證明當無法再尋找到增廣路徑時,就證明當前網絡是最大流網絡呢?這就需要用到最大流最小割定理。
首先介紹下,割的概念。流網絡G(V,E)的割(S,T)將V划分為S和T=V-S兩部分,使得s屬於S,t屬於T。割(S,T)的容量是指從集合S到集合T的所有邊(有方向)的容量之和(不算反方向的,必須是S-àT)。如果f是一個流,則穿過割(S,T)的凈流量被定義為f(S,T)(包括反向的,SàT的為正值,T—>S的負值)。將上面舉的例子繼續拿來,隨便畫一個割,如下圖所示:
割的容量就是c(u,w)+c(v,x)=26
當前流網絡的穿過割的凈流量為f(u,w)+f(v,x)-f(w,v)=12+11-4=19
顯然,我們有對任意一個割,穿過該割的凈流量上界就是該割的容量,即不可能超過割的容量。所以網絡的最大流必然無法超過網絡的最小割。
可是,這跟殘留網絡上的增廣路徑有什么關系呢?
首先,我們必須了解一個特性,根據上一篇文章中講到的最大流問題的線性規划表示時,提到,流網絡的流量守恆的原則,根據這個原則我們可以知道,對網絡的任意割,其凈流量的都是相等的。具體證明是不難的,可以通過下圖形象的理解下,
和上面的割相比,集合S中少了u和v,從源點s到集合T的凈流量都流向了u和v,而在上一個割圖中,集合S到集合T的流量是等於u和v到集合T的凈流量的。其中w也有流流向了u和v,而這部分流無法流向源點s,因為沒有路徑,所以最后這部分流量加上s到u和v的流量,在u和v之間無論如何互相傳遞流,最終都要流向集合T,所以這個流量值是等於s流向u和v的值的。將s比喻成一個水龍頭,u和v流向別處的水流,都是來自s的,其自身不可能創造水流。所以任意割的凈流量都是相等的。
萬事俱備,現在來證明當殘留網絡Gf中不包含增廣路徑時,f是G的最大流。
假設Gf中不包含增廣路徑,即Gf不包含從s到v的路徑,定義S={v:Gf中從s到v存在一條通路},也就是Gf中s能夠有通路到達的點的集合,顯然這個集合不包括t,因為s到t沒有通路。這時,我們令T=V-S。那么(S,T)就是一個割。如下圖所示:
那么,對於頂點u屬於S,v屬於T,有f(u,v)=c(u,v)。否則(u,v)就存在殘余流量,因而s到u加上u到v就構成了一條s到v的通路,所以v就必須屬於S,矛盾。因此這時就表明當前流f是等於當前的割的容量的,因此f就是最大流。
Ford-Fulkerson方法
Ford-Fulkerson方法的正確性依賴於這個定理:當殘存網絡中不存在一條從s到t的增廣路徑,那么該圖已經達到最大流。這個定理的證明及一些與其等同的定理可以參考《算法導論》。
Ford-Fulkerson方法的偽代碼如下。其中<u,v>代表頂點u到頂點v的一條邊,<u,v>.f表示該邊的流量,c是邊容量矩陣,c(i,j)表示邊<i,j>的容量,當邊<i,j>不存在時,c(i,j)=0。e為殘存網絡矩陣,e(i,j)表示邊<i,j>的值,當邊<i,j>不存在時,e(i,j)=0。E表示邊的集合。f表示流網絡。
Ford-Fulkerson for <u,v> ∈ E <u,v>.f = 0 while find a route from s to t in e m = min(<u,v>.f, <u,v> ∈ route) for <u,v> ∈ route if <u,v> ∈ f <u,v>.f = <u,v>.f + m else <v,u>.f = <v,u>.f - m
Ford-Fulkerson方法首先對圖中的所有邊的流量初始化為零值,然后開始進入循環:如果在殘存網絡中可以找到一條從s到t的增廣路徑,那么要找到這條這條路徑上值最小的邊,然后根據該值來更新流網絡。
Ford-Fulkerson有很多種實現,主要不同點在於如何尋找增廣路徑。最開始提出該方法的Ford和Fulkerson同學在其論文中都是使用廣度優先搜索實現的,其時間復雜度為O(VE),整個算法的時間復雜度為O(VE^2)。
代碼實現
Edge:
package com.darrenchan.graph.FordFulkerson; /** * 網絡中的邊 */ public class Edge { private int v1; private int v2; private int capacity; private int flow; public Edge(int v1,int v2,int flow,int capacity){ this.v1 = v1; this.v2 = v2; this.capacity = capacity; this.flow = flow; } public int getV1(){ return v1; } public int getV2(){ return v2; } public int getCapacity(){ return capacity; } public int getFlow(){ return flow; } public void setFlow(int f){ flow = f; } }
Edge2:
package com.darrenchan.graph.FordFulkerson; /** * 殘存網絡中的邊 */ public class Edge2 { private int v1; private int v2; private int flow; public Edge2(int v1,int v2,int flow){ this.v1 = v1; this.v2 = v2; this.flow = flow; } public int getV1(){ return v1; } public int getV2(){ return v2; } public int getFlow(){ return flow; } public void setFlow(int f){ flow = f; } }
Gf:
package com.darrenchan.graph.FordFulkerson; import java.util.LinkedList; import java.util.Queue; public class Gf { private int vNum; private int eNum; private LinkedList<Edge2>[] GLists; public Gf(int n){ vNum = n; eNum = 0; GLists = new LinkedList[n]; for(int i = 0;i<n;i++) GLists[i] = new LinkedList<>(); } public void insertEdge(Edge2 e){ int v1 = e.getV1(); GLists[v1].add(e); eNum++; } /** * 返回一條增廣路徑 * @return */ public LinkedList<Integer> augmentingPath(){ LinkedList<Integer> list = new LinkedList<>(); Queue<Integer> queue = new LinkedList<>(); int[] reached = new int[vNum]; int[] preNode = new int[vNum]; for(int i = 0;i<vNum;i++){ reached[i] = 0; preNode[i] = -1; } preNode[0] = -1; reached[0] = 1; queue.add(0); while(!queue.isEmpty()){//沒有循環起來 int now = queue.poll(); LinkedList<Edge2> inlist = (LinkedList<Edge2>) GLists[now].clone(); while(!inlist.isEmpty()){ Edge2 e = inlist.pop(); int v2 = e.getV2(); if(reached[v2]==0){ queue.add(v2); reached[v2] = 1; preNode[v2] = now; } } } for(int i = 0;i<vNum;i++){ System.out.println(reached[i]+" "+preNode[i]); } if(reached[vNum-1]==0){ //System.out.println("here"); return list; } int pointnum = vNum-1; while(pointnum!=-1){ list.add(0, pointnum); pointnum = preNode[pointnum]; } return list; } /** * 根據增廣路徑得到需要調整的值 * 找到增廣路徑中最小的邊 * @param list * @return */ public int changeNum(LinkedList<Integer> list){ if(list.equals(null)) return 0; int minchange = 1000; int v1 = 0; for(int i = 1;i<list.size();i++){ int v2 = list.get(i); LinkedList<Edge2> elist = (LinkedList<Edge2>) GLists[v1].clone(); Edge2 edge = elist.pop(); while(edge.getV2()!=v2){ edge = elist.pop(); } if(minchange>edge.getFlow()) minchange = edge.getFlow(); v1 = v2; } return minchange; } public void bianli(){ System.out.println("殘存網絡 共 "+vNum+" 個頂點, "+eNum+" 條邊"); for(int i = 0;i<vNum;i++){ if(GLists[i].size()==0){ System.out.println(i+"沒有后繼"); continue; } for(int j = 0;j<GLists[i].size();j++){ Edge2 e = GLists[i].get(j); System.out.println("[ "+e.getV1()+" , "+e.getV2()+" , "+e.getFlow()+" ]"); } } } }
Graph:
package com.darrenchan.graph.FordFulkerson; import java.util.LinkedList; public class Graph { private int vNum; private int eNum; private Gf gf; private LinkedList<Edge>[] GLists; public Graph(int n){ vNum = n; eNum = 0; GLists = new LinkedList[n]; for(int i = 0;i<n;i++) GLists[i] = new LinkedList<>(); } public void insertEdge(Edge e){ int v1 = e.getV1(); GLists[v1].add(e); eNum++; } public void produceGf(){ gf = new Gf(vNum); for(int i = 0;i<vNum;i++){ LinkedList<Edge> list = (LinkedList<Edge>) GLists[i].clone(); while(!list.isEmpty()){ Edge edge = list.pop(); int v1 = edge.getV1(); int v2 = edge.getV2(); int flow = edge.getFlow(); int capacity = edge.getCapacity(); if(flow==0){ gf.insertEdge(new Edge2(v1,v2,capacity)); }else{ if(flow==capacity){ gf.insertEdge(new Edge2(v2,v1,capacity)); }else if(flow<capacity){ gf.insertEdge(new Edge2(v1,v2,capacity-flow)); gf.insertEdge(new Edge2(v2,v1,flow)); } } } } } public Gf getGf(){ return gf; } private LinkedList<Integer> augmentingPath(){ return gf.augmentingPath(); } private int changeNum(LinkedList<Integer> list){ return gf.changeNum(list); } /** * 最大流 */ public void MaxFlow(){ produceGf(); gf.bianli(); LinkedList<Integer> list = augmentingPath(); while(list.size()>0){ int changenum = changeNum(list); LinkedList<Integer> copylist = (LinkedList<Integer>) list.clone();//調試 System.out.println("list:"); while(!copylist.isEmpty()){ System.out.print(copylist.pop()+" "); } System.out.println(); System.out.println("changenum: "+changenum); int v1 = 0; for(int i = 1;i<list.size();i++){ int v2 = list.get(i); if(!GLists[v1].isEmpty()){ int j = 0; Edge e = GLists[v1].get(j); while(e.getV2()!=v2 && j<GLists[v1].size()){ e = GLists[v1].get(j); j++; } if(e.getV2()!=v2 && j==GLists[v1].size()){//調試 j = 0; e = GLists[v2].get(j); while(e.getV2()!=v1 && j<GLists[v2].size()){ e = GLists[v2].get(j); j++; } } e.setFlow(e.getFlow()+changenum); } v1 = v2; } bianli(); produceGf(); gf.bianli(); list = augmentingPath(); } } public void bianli(){ System.out.println("共有 "+vNum+" 個頂點, "+eNum+" 條邊"); for(int i = 0;i<vNum;i++){ if(GLists[i].size()==0) continue; for(int j = 0;j<GLists[i].size();j++){ Edge e = GLists[i].get(j); System.out.println("[ "+e.getV1()+" , "+e.getV2()+" , "+e.getFlow()+" , "+e.getCapacity()+" ]"); } } } public void showResult(){ bianli(); int maxflow = 0; for(int i = 0;i<vNum;i++){ if(GLists[i].size()>0){ for(int j = 0;j<GLists[i].size();j++){ if(GLists[i].get(j).getV2() == vNum-1){ maxflow += GLists[i].get(j).getFlow(); } } } } System.out.println("最大流為 "+maxflow); } }
Main:
package com.darrenchan.graph.FordFulkerson; public class Main { public static void main(String[] args){ test(); } private static void test(){ Graph graph = new Graph(6); Edge[] edges = new Edge[9]; edges[0] = new Edge(0,1,0,16); edges[1] = new Edge(0,2,0,13); edges[2] = new Edge(1,3,0,12); edges[3] = new Edge(2,1,0,4); edges[4] = new Edge(2,4,0,14); edges[5] = new Edge(3,2,0,9); edges[6] = new Edge(3,5,0,20); edges[7] = new Edge(4,3,0,7); edges[8] = new Edge(4,5,0,4); for(int i = 0;i<9;i++) graph.insertEdge(edges[i]); graph.MaxFlow(); graph.showResult(); } public static void test2(){ Graph graph = new Graph(6); Edge[] edges = new Edge[9]; edges[0] = new Edge(0,1,4,16); edges[1] = new Edge(0,2,0,13); edges[2] = new Edge(1,3,4,12); edges[3] = new Edge(2,1,0,4); edges[4] = new Edge(2,4,4,14); edges[5] = new Edge(3,2,4,9); edges[6] = new Edge(3,5,0,20); edges[7] = new Edge(4,3,0,7); edges[8] = new Edge(4,5,4,4); for(int i = 0;i<9;i++) graph.insertEdge(edges[i]); graph.bianli(); graph.MaxFlow(); graph.bianli(); } }
運行結果:
殘存網絡 共 6 個頂點, 9 條邊
[ 0 , 1 , 16 ]
[ 0 , 2 , 13 ]
[ 1 , 3 , 12 ]
[ 2 , 1 , 4 ]
[ 2 , 4 , 14 ]
[ 3 , 2 , 9 ]
[ 3 , 5 , 20 ]
[ 4 , 3 , 7 ]
[ 4 , 5 , 4 ]
5沒有后繼
1 -1
1 0
1 0
1 1
1 2
1 3
list:
0 1 3 5
changenum: 12
共有 6 個頂點, 9 條邊
[ 0 , 1 , 12 , 16 ]
[ 0 , 2 , 0 , 13 ]
[ 1 , 3 , 12 , 12 ]
[ 2 , 1 , 0 , 4 ]
[ 2 , 4 , 0 , 14 ]
[ 3 , 2 , 0 , 9 ]
[ 3 , 5 , 12 , 20 ]
[ 4 , 3 , 0 , 7 ]
[ 4 , 5 , 0 , 4 ]
殘存網絡 共 6 個頂點, 11 條邊
[ 0 , 1 , 4 ]
[ 0 , 2 , 13 ]
[ 1 , 0 , 12 ]
[ 2 , 1 , 4 ]
[ 2 , 4 , 14 ]
[ 3 , 1 , 12 ]
[ 3 , 2 , 9 ]
[ 3 , 5 , 8 ]
[ 4 , 3 , 7 ]
[ 4 , 5 , 4 ]
[ 5 , 3 , 12 ]
1 -1
1 0
1 0
1 4
1 2
1 4
list:
0 2 4 5
changenum: 4
共有 6 個頂點, 9 條邊
[ 0 , 1 , 12 , 16 ]
[ 0 , 2 , 4 , 13 ]
[ 1 , 3 , 12 , 12 ]
[ 2 , 1 , 0 , 4 ]
[ 2 , 4 , 4 , 14 ]
[ 3 , 2 , 0 , 9 ]
[ 3 , 5 , 12 , 20 ]
[ 4 , 3 , 0 , 7 ]
[ 4 , 5 , 4 , 4 ]
殘存網絡 共 6 個頂點, 13 條邊
[ 0 , 1 , 4 ]
[ 0 , 2 , 9 ]
[ 1 , 0 , 12 ]
[ 2 , 0 , 4 ]
[ 2 , 1 , 4 ]
[ 2 , 4 , 10 ]
[ 3 , 1 , 12 ]
[ 3 , 2 , 9 ]
[ 3 , 5 , 8 ]
[ 4 , 2 , 4 ]
[ 4 , 3 , 7 ]
[ 5 , 3 , 12 ]
[ 5 , 4 , 4 ]
1 -1
1 0
1 0
1 4
1 2
1 3
list:
0 2 4 3 5
changenum: 7
共有 6 個頂點, 9 條邊
[ 0 , 1 , 12 , 16 ]
[ 0 , 2 , 11 , 13 ]
[ 1 , 3 , 12 , 12 ]
[ 2 , 1 , 0 , 4 ]
[ 2 , 4 , 11 , 14 ]
[ 3 , 2 , 0 , 9 ]
[ 3 , 5 , 19 , 20 ]
[ 4 , 3 , 7 , 7 ]
[ 4 , 5 , 4 , 4 ]
殘存網絡 共 6 個頂點, 13 條邊
[ 0 , 1 , 4 ]
[ 0 , 2 , 2 ]
[ 1 , 0 , 12 ]
[ 2 , 0 , 11 ]
[ 2 , 1 , 4 ]
[ 2 , 4 , 3 ]
[ 3 , 1 , 12 ]
[ 3 , 2 , 9 ]
[ 3 , 5 , 1 ]
[ 3 , 4 , 7 ]
[ 4 , 2 , 11 ]
[ 5 , 3 , 19 ]
[ 5 , 4 , 4 ]
1 -1
1 0
1 0
0 -1
1 2
0 -1
共有 6 個頂點, 9 條邊
[ 0 , 1 , 12 , 16 ]
[ 0 , 2 , 11 , 13 ]
[ 1 , 3 , 12 , 12 ]
[ 2 , 1 , 0 , 4 ]
[ 2 , 4 , 11 , 14 ]
[ 3 , 2 , 0 , 9 ]
[ 3 , 5 , 19 , 20 ]
[ 4 , 3 , 7 , 7 ]
[ 4 , 5 , 4 , 4 ]
最大流為 23
參考:
https://blog.csdn.net/john_bian/article/details/74734243