Dinic演算法的原理與構造

2021-07-24 03:08:11 字數 4211 閱讀 3112

出處:

為了更好的介紹dinic演算法,我們先來介紹最短增廣路演算法。

最短增廣路演算法

1、頂點的層次和層次網路

頂點的層次:在殘留網路中,把從源點到頂點u的最短路徑長度(該長度僅僅是值路徑上邊的數目,與容量無關),稱為頂點u的層次,記為level(u)。源點vs的層次為0。

將殘留網路中所有的頂點的層次標註出來的過程稱為分層。

注意:

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

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

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

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

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

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

層次網路:對殘留網路進行分層後,刪去比匯點vt層次更高的頂點和與匯點vt同層的頂點(保留vt),並刪去這些頂點相關聯的弧,再刪去從某層頂點指向同層頂點和低層頂點的弧,所剩餘的各條弧的容量與殘留網路中的容量相同,這樣得到的網路就是殘留網路的子網路,稱為層次網路,記為g''(v'',e'')。

根據層次網路定義,層次網路中任意的一條弧,有滿足level(u)+1 == level(v),這條弧也叫允許弧。直觀的說,層次網路是建立在殘留網路基礎之上的一張「最短路徑圖」。從源點開始,在層次網路中沿著邊不管怎麼走,到達乙個終點之後,經過的路徑一定是終點在殘留網路中的最短路徑。

阻塞流:設容量網路的乙個可行流為f,當該網路的層次g''中不存在增廣路(即從源點vs到匯點vt的路徑時),稱該可行流f為層次網路g''的阻塞流。

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)。

下面給出 dinic具體實現的**。

預處理:

[cpp]view plain

copy

#include 

#include 

#include 

#include 

#include 

#include 

#include 

using

namespace

std;  

const

intmaxn = 210;  

const

intmaxm = 210*210;  

const

intinf = 0x3f3f3f3f;  

struct

edge  

edge[maxm];  

intn, m;  

intcnt;  

intfirst[maxn], level[maxn];  

intq[maxn];  

void

init()  

void

read_graph(

intu, 

intv, 

intf)    

bfs構建層次網路:

[cpp]view plain

copy

intbfs(

ints, 

intt) 

//構建層次網路 

}  }  return

0;  

}  

dfs找尋增廣路:

[cpp]view plain

copy

intdfs(

intu, 

intmaxf, 

intt)  

}  return

ret;  

}  

dinic:

[cpp]view plain

copy

intdinic(

ints, 

intt) 

//dinic  

dinic演算法的改進

儲存鄰接表是使用的是head陣列,現在另設乙個head2陣列,儲存的是每個節點x從head2 x 開始的邊才會有增廣路,這樣減少了無用邊的迴圈。並且head2陣列只在全域性初始化一次,即如果沒有增廣,該值只會逐漸減小直到0。在hdu3572中使用該優化可使時間從998ms優化值156ms 附上 注釋...

Dinic演算法的程式實現

program poj 1273 dinic author comzyh include include include include define min x,y x0 if dis n 0 return 1 else return 0 匯點的dis小於零,表明bfs不到匯點 find代表一次增...

關於Dinic演算法的幾點討論

dinic演算法是經典的網路最大流演算法,該演算法由在ek演算法的基礎上增加 分層 這一概念得到。演算法複雜度為 分層操作 演算法主體 dinic 最終,我們在不斷分層和由源點進行dinic演算法的過程中獲得了整體的最大流。include include include includeusing n...