1. 程式人生 > >最大流之Ford-Fulkerson演算法(C++實現)

最大流之Ford-Fulkerson演算法(C++實現)

本文主要講解最大流問題的Ford-Fulkerson解法。可是說這是一種方法,而不是演算法,因為它包含具有不同執行時間的幾種實現。該方法依賴於三種重要思想:殘留網路,增廣路徑和割。

一、殘留網路

顧名思義,殘留網路是指給定網路和一個流,其對應還可以容納的流組成的網路。具體說來,就是假定一個網路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就是最大流。

 

程式碼:

main.cpp

// C++ program for implementation of Ford Fulkerson algorithm
#include <iostream>
#include <limits.h>
#include <string.h>
#include <queue>
using namespace std;

// Number of vertices in given graph
#define V 6


/* Returns true if there is a path from source 's' to sink 't' in
  residual graph. Also fills parent[] to store the path */
bool bfs(int rGraph[V][V], int s, int t, int parent[])
{
    // Create a visited array and mark all vertices as not visited
    bool visited[V];
    memset(visited, 0, sizeof(visited));

    // Create a queue, enqueue source vertex and mark source vertex as visited
    queue<int> q;
    q.push(s);
    visited[s] = true;
    parent[s] = -1;

    // Standard BFS Loop
    int u;
    while (!q.empty())
    {
        // edge: u -> v
        u = q.front();  // head point u
        q.pop();
        for (int v = 0; v < V; ++v)  // tail point v
        {
            if (!visited[v] && rGraph[u][v] > 0)  // find one linked vertex
            {
                q.push(v);
                parent[v] = u;  // find pre point
                visited[v] = true;
            }
        }
    }

    // If we reached sink in BFS starting from source, then return true, else false
    return visited[t] == true;
}

// Returns the maximum flow from s to t in the given graph
int fordFulkerson(int graph[V][V], int s, int t)
{
    int u, v;
    int rGraph[V][V];

    for (u = 0; u < V; ++u)
    {
        for (v = 0; v < V; ++v)
        {
            rGraph[u][v] = graph[u][v];
        }
    }

    int parent[V];
    int max_flow = 0;

    // Augment the flow while tere is path from source to sink
    while (bfs(rGraph, s, t, parent))
    {
        // edge: u -> v
        int path_flow = INT_MAX;
        for (v = t; v != s; v = parent[v])
        {
            // find the minimum flow
            u = parent[v];
            path_flow = min(path_flow, rGraph[u][v]);
        }

        // update residual capacities of the edges and reverse edges along the path
        for (v = t; v != s; v = parent[v])
        {
            u = parent[v];
            rGraph[u][v] -= path_flow;
            rGraph[v][u] += path_flow;  // assuming v->u weight is add path_flow
        }

        // Add path flow to overall flow
        max_flow += path_flow;
    }

    return max_flow;
}

int main()
{
    int graph[V][V] = { {0,16,13, 0, 0, 0},
                        {0, 0,10,12, 0, 0},
                        {0, 4, 0, 0,14, 0},
                        {0, 0, 9, 0, 0,20},
                        {0, 0, 0, 7, 0, 4},
                        {0, 0, 0, 0, 0, 0}
                      };
    cout << "the maximum flow from v0 to v5 is:" << endl << fordFulkerson(graph, 0, 5);
    return 0;
}

示意圖:

執行結果:

 

注:

  1. 殘差圖其實相當於是在當前狀況下,該圖還允許進行改變的限度的一種表示
  2. 每次用BFS找到一種可以從s到t的路徑即可,直到找不到路徑為止
  3. 可以將最大流問題想象成水管排水問題,邊為管道,權值weight為管道的實際排水量,capacity為管道的容量。

 

參考資料:

https://www.geeksforgeeks.org/ford-fulkerson-algorithm-for-maximum-flow-problem/

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