Dinic算法詳解及實現


預備知識:

殘留網絡:設有容量網絡G(V,E)及其上的網絡流f,G關於f的殘留網絡即為G(V',E'),其中G’的頂點集V'和G的頂點集V相同,即V'=V,對於G中任何一條弧<u,v>,如果f(u,v)<c(u,v),那么在G'中有一條弧<u,v>∈E',其容量為c'(u,v)=c(u,v)-f(u,v),如果f(u,v)>0,則在G'中有一條弧<v,u>∈E',其容量為c’(v,u)=f(u,v).

從殘留網絡的定義來看,原容量網絡中的每條弧在殘留網絡中都化為一條或者兩條弧。在殘留網絡中,從源點到匯點的任意一條簡單路徑都對應一條增光路,路徑上每條弧容量的最小值即為能夠一次增廣的最大流量。

頂點的層次:在殘留網絡中,把從源點到頂點u的最短路徑長度,稱為頂點u的層次。源點 Vs的層次為0.例如下圖就是一個分層的過程。

 

注意:

(1)對殘留網路進行分層后,弧可能有3種可能的情況。

1、從第i層頂點指向第i+1層頂點。

2、從第i層頂點指向第i層頂點。

3、從第i層頂點指向第j層頂點(j < i)。

(2)不存在從第i層頂點指向第i+k層頂點的弧(k>=2)。

(3)並非所有的網絡都能分層。

 

2、最短路增廣路徑的算法思想

最短增廣路的算法思想是:每次在層次網絡中找一條含弧數最少的增廣路進行增廣。最短增廣路算法的具體步驟如下:

(1)初始化容量網絡和網絡流。

(2)構造殘留網絡和層次網絡,若匯點不在層次網絡中,則算法結束。

(3)在層次網絡中不斷用BFS增廣,直到層次網絡中沒有增廣路為止;每次增廣完畢,在層次網絡中要去掉因改進流量而導致飽和的弧。

(4)轉步驟(2)。

在最短增廣路算法中,第(2)、(3)步被循環執行,將執行(2)、(3)步的一次循環稱為一個階段。在每個階段中,首先根據殘留網絡建立層次網絡,然后不斷用BFS在層次網絡中增廣,直到出現阻塞流。注意每次增廣后,在層次網絡中要去掉因改進流量而飽和的弧。該階段的增廣完畢后,進入下一階段。這樣的不斷重復,直到匯點不在層次網絡中為止。匯點不在層次網絡中意味着在殘留網絡中不存在一條從源點到匯點的路徑,即沒有增廣路。

在程序實現的時候,並不需要真正“構造”層次網絡,只需要對每個頂點標記層次,增廣的時候,判斷邊是否滿足level(v) = level(u)+1這一約束條件即可。

3、最短增廣路算法復雜度分析

最短增廣路的復雜度包括建立層次網絡和尋找增廣路兩部分。

在最短增廣路中,最多建立n個層次網絡,每個層次網絡用BFS一次遍歷即可得到。一次BFS的復雜度為O(m),所以建層次圖的總復雜度為O(n*m)。

每增廣一次,層次網絡中必定有一條邊會被刪除。層次網絡中最多有m條邊,所以認為最多可以增廣m次。在最短增廣路算法中,用BFS來增廣,一次增廣的復雜度為O(n+m),其中O(m)為BFS的花費,O(n)為修改流量的花費。所以在每一階段尋找增廣路的復雜度為O(m*(m+n)) = O(m*m)。因此n個階段尋找增廣路的算法總復雜度為O(n*m*m)。

兩者之和為O(n*m*m)。

以上介紹最短增廣路算法只是為下面介紹Dinic算法而提供給大家一個鋪墊,有了以上的基礎,接下來我們來介紹Dinic算法,Dinic其實是最短增廣路算法的優化版。

 

 

 

連續最短增廣路算法----Dinic算法

1、Dinic算法思路

Dinic算法的思想也是分階段地在層次網絡中增廣。它與最短增廣路算法不同之處是:最短增廣路每個階段執行完一次BFS增廣后,要重新啟動BFS從源點Vs開始尋找另一條增廣路;而在Dinic算法中,只需一次DFS過程就可以實現多次增廣,這是Dinic算法的巧妙之處。Dinic算法具體步驟如下:

(1)初始化容量網絡和網絡流。

(2)構造殘留網絡和層次網絡,若匯點不再層次網絡中,則算法結束。

(3)在層次網絡中用一次DFS過程進行增廣,DFS執行完畢,該階段的增廣也執行完畢。

(4)轉步驟(2)。

在Dinic的算法步驟中,只有第(3)步與最短增廣路相同。在下面實例中,將會發現DFS過程將會使算法的效率有非常大的提高。

Dinic算法復雜度分析

與最短增廣路算法一樣,Dinic算法最多被分為n個階段,每個階段包括建層次網絡和尋找增廣路兩部分,其中建立層次網絡的復雜度仍是O(n*m)。

現在來分析DFS過程的總復雜度。在每一階段,將DFS分成兩部分分析。

(1)修改增廣路的流量並后退的花費。在每一階段,最多增廣m次,每次修改流量的費用為O(n)。而一次增廣后在增廣路中后退的費用也為O(n)。所以在每一階段中,修改增廣路以及后退的復雜度為O(m*n)。

(2)DFS遍歷時的前進與后退。在DFS遍歷時,如果當前路徑的最后一個頂點能夠繼續擴展,則一定是沿着第i層的頂點指向第i+1層頂點的邊向匯點前進了一步。因為增廣路經長度最長為n,所以最壞的情況下前進n步就會遇到匯點。在前進的過程中,可能會遇到沒有邊能夠沿着繼續前進的情況,這時將路徑中的最后一個點在層次圖中刪除。

注意到每后退一次必定會刪除一個頂點,所以后退的次數最多為n次。在每一階段中,后退的復雜度為O(n)。

假設在最壞的情況下,所有的點最后均被退了回來,一共共后退了n次,這也就意味着,有n次的前進被“無情”地退了回來,這n次前進操作都沒有起到“尋找增廣路”的作用。除去這n次前進和n次后退,其余的前進都對最后找到增廣路做了貢獻。增廣路最多找到m次。每次最多前進n個點。所以所有前進操作最多為n+m*n次,復雜度為O(n*m)。

於是得到,在每一階段中,DFS遍歷時前進與后退的花費為O(m*n)。

綜合以上兩點,一次DFS的復雜度為O(n*m)。因此,Dinic算法的總復雜度即O(m*n*n)。

 

 

下面的實現,有借鑒別人的方法。主要是利用BFS構建層次網絡,這里用level數組來存儲每個頂點的層數。

然后利用dfs進行增廣,默認M個節點,第M個節點就是匯點。然后當第M個節點不在分層網絡時,就結束。

 

#include <iostream>
#include <queue>
using namespace std;

const int INF = 0x7fffffff;
int V, E;
int level[205];
int Si, Ei, Ci;

struct Dinic
{
    int c;
    int f;
}edge[205][205];

bool dinic_bfs()      //bfs方法構造層次網絡
{
    queue<int> q;
    memset(level, 0, sizeof(level));
    q.push(1);
    level[1] = 1;
    int u, v;
    while (!q.empty()) {
        u = q.front();
        q.pop();
        for (v = 1; v <= E; v++) {
            if (!level[v] && edge[u][v].c>edge[u][v].f) {
                level[v] = level[u] + 1;
                q.push(v);
            }
        }
    }
    return level[E] != 0;                //question: so it must let the sink node is the Mth?/the way of yj is give the sink node's id
}

int dinic_dfs(int u, int cp) {           //use dfs to augment the flow
    int tmp = cp;
    int v, t;
    if (u == E)
        return cp;
    for (v = 1; v <= E&&tmp; v++) {
        if (level[u] + 1 == level[v]) {
            if (edge[u][v].c>edge[u][v].f) {
                t = dinic_dfs(v, min(tmp, edge[u][v].c - edge[u][v].f));
                edge[u][v].f += t;
                edge[v][u].f -= t;
                tmp -= t;
            }
        }
    }
    return cp - tmp;
}
int dinic() {
    int sum=0, tf=0;
    while (dinic_bfs()) {
        while (tf = dinic_dfs(1, INF))
            sum += tf;
    }
    return sum;
}

int main() {
    while (scanf("%d%d", &V, &E)) {
        memset(edge, 0, sizeof(edge));
        while (V--) {
            scanf("%d%d%d", &Si, &Ei, &Ci);
            edge[Si][Ei].c += Ci;
        }
        int ans = dinic();
        printf("%d\n", ans);
    }
    return 0;
}

 


免責聲明!

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



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