[數據結構]最大流之Ford-Fulkerson算法


本文主要講解最大流問題的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

https://blog.csdn.net/smartxxyx/article/details/9293665/

https://blog.csdn.net/ivan_zgj/article/details/51580993


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM