august 7, 2013 /
程式設計指南
isap 是圖論求最大流的演算法之一,它很好的平衡了執行時間和程式複雜度之間的關係,因此非常常用。
我們使用鄰接表來表示圖,表示方法可以見文章帶權最短路 dijkstra, spfa, bellman-ford, asp, floyd-warshall 演算法分析或二分圖的最大匹配、完美匹配和匈牙利演算法的開頭(就不重複貼**了)。在下文中,圖的源點(source)表示為
s ,匯點(sink)表示為
t ,當前節點為
u 。建圖時,需要建立雙向邊(設反向的邊容量為
0 )才能保證演算法正確。
求解最大流問題的乙個比較容易想到的方法就是,每次在殘量網路(residual network)中任意尋找一條從
s 到
t 的路徑,然後增廣,直到不存在這樣的路徑為止。這就是一般增廣路演算法(labeling algorithm)。可以證明這種不加改進的貪婪演算法是正確的。假設最大流是
f ,那麼它的執行時間為o(
f⋅∣e
∣)。但是,這個執行時間並不好,因為它和最大流
f 有關。
人們發現,如果每次都沿著殘量網路中的最短增廣路增廣,則執行時間可以減為 o(
∣e∣2
⋅∣v∣
) 。這就是最短增廣路演算法。而 isap 演算法則是最短增廣路演算法的乙個改進。其實,isap 的意思正是「改進的最短增廣路」 (improved shortest augmenting path)。
順便說一句,上面討論的所有演算法根本上都屬於增廣路方法(ford-fulkerson method)。和它對應的就是大名鼎鼎的預流推進方法(preflow-push method)。其中最高標號預流推進演算法(highest-label preflow-push algorithm)的複雜度可以達到o(
∣v∣2
∣e∣−
−−√)
。雖然在複雜度上比增廣路方法進步很多,但是預流推進演算法複雜度的上界是比較緊的,因此有時差距並不會很大。
概括地說,isap 演算法就是不停地找最短增廣路,找到之後增廣;如果遇到死路就 retreat,直到發現
s ,
t 不連通,演算法結束。找最短路本質上就是無權最短路徑問題,因此採用 bfs 的思想。具體來說,使用乙個陣列
d ,記錄每個節點到匯點
t 的最短距離。搜尋的時候,只沿著滿足d[
u]=d
[v]+
1 的邊u→
v (這樣的邊稱為允許弧)走。顯然,這樣走出來的一定是最短路。
原圖存在兩種子圖,乙個是殘量網路,乙個是允許弧組成的圖。殘量網路保證可增廣,允許弧保證最短路(時間界較優)。所以,在尋找增廣路的過程中,一直是在殘量網路中沿著允許弧尋找。因此,允許弧應該是屬於殘量網路的,而非原圖的。換句話說,我們沿著允許弧,走的是殘量網路(而非原圖)中的最短路徑。當我們找到沿著殘量網路找到一條增廣路,增廣後,殘量網路肯定會變化(至少少了一條邊),因此決定允許弧的
d 陣列要進行相應的更新(順便提一句,dinic 的做法就是每次增廣都重新計算
d 陣列)。然而,isap 「改進」的地方之一就是,其實沒有必要馬上更新
d 陣列。這是因為,去掉一條邊只可能令路徑變得更長,而如果增廣之前的殘量網路存在另一條最短路,並且在增廣後的殘量網路中仍存在,那麼這條路徑毫無疑問是最短的。所以,isap 的做法是繼續增廣,直到遇到死路,才執行 retreat 操作。
說到這裡,大家應該都猜到了,retreat 操作的主要任務就是更新
d 陣列。那麼怎麼更新呢?非常簡單:假設是從節點
u 找遍了鄰接邊也沒找到允許弧的;再設一變數
m ,令
m 等於殘量網路中
u 的所有鄰接點的
d 陣列的最小值,然後令d[
u]等於m+
1 即可。這是因為,進入 retreat 環節說明殘量網路中
u 和
t 已經不能通過(已過時)的允許弧相連,那麼
u 和
t 實際上在殘量網路中的最短路的長是多少呢?(這正是
d 的定義!)顯然是殘量網路中
u 的所有鄰接點和
t 的距離加
1 的最小情況。特殊情況是,殘量網路中
u 根本沒有鄰接點。如果是這樣,只需要把d[
u]設為乙個比較大的數即可,這會導致任何點到
u 的邊被排除到殘量網路以外。(嚴格來說只要大於等於∣v
∣ 即可。由於最短路一定是無環的,因此任意路徑長最大是∣v
∣−1 )。修改之後,只需要把正在研究的節點
u講到這裡,isap 演算法的框架內容就講完了。對於**本身,還有幾個優化和實現的技巧需要說明。
演算法執行之前需要用 bfs 初始化
d 陣列,方法是從
t 到
s 逆向進行。
演算法主體需要維護乙個「當前節點」
u ,執行這個節點的前進、retreat 等操作。
記錄路徑的方法非常簡單,宣告乙個陣列
p ,令p[
i]等於增廣路上到達節點
i 的邊的序號(這樣就可以找到從哪個頂點到的頂點
i )。需要路徑的時候反向追蹤一下就可以了。
判斷殘量網路中s,
t 不連通的條件,就是d[
s]≥∣v
∣ 。這是因為當s,
t 不連通時,最終殘量網路中
s 將沒有任何鄰接點,對
s 的 retreat 將導致上面條件的成立。
gap 優化。gap 優化可以提前結束程式,很多時候提速非常明顯(高達 100 倍以上)。gap 優化是說,進入 retreat 環節後,u,
t 之間的連通性消失,但如果
u 是最後乙個和
t 距離d[
u](更新前)的點,說明此時s,
t 也不連通了。這是因為,雖然u,
t 已經不連通,但畢竟我們走的是最短路,其他點此時到
t 的距離一定大於d[
u](更新前),因此其他點要到
t ,必然要經過乙個和
t 距離為d[
u](更新前)的點。gap 優化的實現非常簡單,用乙個陣列記錄並在適當的時候判斷、跳出迴圈就可以了。
另乙個優化,就是用乙個陣列儲存乙個點已經嘗試過了哪個鄰接邊。尋找增廣的過程實際上類似於乙個 bfs 過程,因此之前處理過的鄰接邊是不需要重新處理的(殘量網路中的邊只會越來越少)。具體實現方法直接看**就可以,非常容易理解。需要注意的一點是,下次應該從上次處理到的鄰接邊繼續處理,而非從上次處理到的鄰接邊的下一條開始。
最後說一下增廣過程。增廣過程非常簡單,尋找增廣路成功(當前節點處理到
t )後,沿著你記錄的路徑走一遍,記錄一路上的最小殘量,然後從
s 到
t 更新流量即可。
int source; // 源點
int sink; // 匯點
int p[max_nodes]; // 可增廣路上的上一條弧的編號
int num[max_nodes]; // 和 t 的最短距離等於 i 的節點數量
int cur[max_nodes]; // 當前弧下標
int d[max_nodes]; // 殘量網路中節點 i 到匯點 t 的最短距離
bool visited[max_nodes];
// 預處理, 反向 bfs 構造 d 陣列
bool bfs()}}
return visited[source];
}// 增廣
int augment()
u = sink;
// 從匯點到源點更新流量
while (u != source)
return df;
}int max_flow()
bool advanced = false;
for (int i = cur[u]; i < g[u].size(); i++)
}if (!advanced)
}return flow;
}
**: www.renfei.org/blog/isap.html
網路流 最大流 ISAP 模板
至於怎麼找到的呢 我才不會說是當初搜網路流乙個個都不理解然後翻到十幾頁點進去看到的 吶 貌似是 國立台灣師範大學 的 這學校是不是這個名 繁體我不會認啊好尷尬 前言 首先不知道這是 isap 還是 sap.作為乙個剛學網路流的蒟蒻 frocean 感覺這網路流真是個玄學的東西 跟著標打模板 0分 0...
模板 網路最大流ISAP
模板 網路最大流isap 建反邊從t到s,bfs跑記錄每個點所在層,gap記錄每層的點數 include using namespace std typedef long long ll const ll max n 1e6 const ll max m 1e6 const ll max e max...
網路最大流 ISAP演算法詳解與模板
isap演算法 isap improved shortest augumenting path 演算法是改進版的sap演算法,如果對效率要求很高的時候,可以用該演算法。1 概述 演算法基於這樣的乙個事實 每次增廣之後,任意結點到匯點 在殘餘網路中 的最短距離都不會減小。這樣,我們可以利用d i 表示...