網絡流(3)——找到最小st-剪切

  在大規模戰爭中,後勤補給是重中之重,爲了盡最大可能知足前線的物資消耗,後勤部隊必然要充分利用每條運輸網。與此同時,交戰雙方也想要以最小的代價切斷敵軍的補給,從而使敵軍處於孤立無援的境地。在古今中外的各類重大戰役中,上演了一幕幕補給線上的攻防戰。算法

甲軍的運輸路線

  假設甲、乙兩軍正在交戰,圖8.17是甲軍的補給運輸網,其中t是甲軍的前沿陣地,s是後勤大營,每條邊是一條公路,邊上的數字表明公路的寬度。安全

  若是甲軍想要盡最大努力供應前線的消耗,應該怎樣設計運輸路線?網絡

  這個問題很容易規約成網絡流模型,使用下面的代碼能夠直接計算出結果。app

 1 import network_flow as nf
 2
 3 V = [0, 1, 2, 3, 4, 5, 6, 7, 8]
 4 E = [nf.Edge(0, 1, 15), nf.Edge(0, 2, 15), nf.Edge(0, 3, 15), nf.Edge(1, 4, 2), nf.Edge(2, 4, 3),
 5      nf.Edge(2, 5, 2), nf.Edge(3, 5, 4), nf.Edge(4, 5, 2), nf.Edge(4, 6, 2), nf.Edge(5, 6, 4),
 6      nf.Edge(5, 7, 3), nf.Edge(6, 8, 15), nf.Edge(7, 8, 15)]
 7 s, t = 0, 8
 8 G = nf.Network(V, E, s, t)
 9 ford_fullkerson = nf.FordFulkerson(G)
10 ford_fullkerson.start()
11 ford_fullkerson.display()
12 X, Y, st_cut = ford_fullkerson.min_st_cut()
13 ford_fullkerson.display_st_cut(X, Y, st_cut)

  (network_flow參考上一章的相關代碼)運行結果對應的網絡流:post

乙軍的轟炸目標

  甲軍想要充分利用每條公路,乙軍的目的正好相反,是破壞公路網,使乙軍的戰鬥部隊處於孤立無援的境地。乙軍打算組織一次針對甲軍補給線的戰略轟炸,因爲甲軍在每一個節點都部署了大量防空武器,所以須要繞過節點,直接轟炸防護薄弱的公路。假設炸掉容量爲1的公路須要n顆炸彈,破壞的公路容量和投擲的炸彈數成正比,怎樣設計轟炸目標才能以最小的代價徹底破壞敵軍的補給線?學習

  一個方案是炸燬直接通向匯點的公路,但因爲鏈接匯點的兩條公路太寬,徹底破壞須要30n的炸彈,這顯然不是最小代價。若是換個地點,假設轟炸的是v5→v7,那麼只須要3n的炸彈就可使寬敞的v7→t淪爲擺設。爲了設計這種轟炸方案,須要理解最小st-剪切的概念。spa

8.3.3 最小st-剪切

  設計成本最低的轟炸方案是咱們的目標,直接尋找起來比較困難,幸而這個目標與網絡的最小剪切有密切關係。設計

  一個流網絡的頂點能夠劃分紅兩個不相交的集合XY,源點s和匯點t分屬於這兩個集合,鏈接XY的邊稱爲這個流網絡的st-剪切(st-cut,也稱爲截、割或切割)。咱們用淺色圓圈表示包含源點的集合X,深色圓圈表示包含匯點的集合Y,這樣就很容易看出一個流網絡的st-剪切:3d

  既然st-剪切是邊的集合,那麼集合中邊的容量之和就是st-剪切的容量。一個流網絡有不少種不一樣的st-剪切,其中容量最小的一個就是最小st-剪切。code

  st-剪切包含了全部源點到匯點的通道,一個顯而易見的結論是:st-剪切的流值等於這個網絡流的值。更進一步,任何網絡流的值都不會超過st-剪切的容量,這也意味着st-剪切表明着流網絡的瓶頸,最小st-剪切的容量不會小於最大流的值,這個定理稱爲最大流-最小剪切定理。該定理也能夠反過來表述:網絡流的值最大不會大於任意一個給定的st-剪切的容量。當X只包含源點或Y只包含匯點時,最大流-最小剪切定理最爲直觀。

  最小st-剪切表明補給線上最難走或最重要的路段,只要破壞這些路段,就能以最小的代價掐斷敵軍的補給,即便只破壞了一部分,也能有效下降敵軍的補給能力。既然最小st-剪切和最大流存在關聯關係,咱們就能夠在尋找最大流時順帶找出最小st-剪切,這仍然須要使用殘存網。在殘存網中,將源點和從源點出發能夠到達的頂點看做集合X,剩下的頂點看做集合Y,對於邊vw,若是知足v屬於Xw屬於Y,那麼vw就是最小st-剪切中的一條邊。

  如下圖爲例,在殘存網中源點可以到達的頂點只有v3,原網絡的最小st-剪切是:

  能夠根據這種思路在FordFulkerson中添加尋找最小st-剪切的實現。

 1 class FordFulkerson():
 2     def __init__(self, G:Network):
 3         self.G = G
 4         self.max_flow = 0  # 最大流
 5     …… (省略部分參考上一章的相關代碼)
 6     def min_st_cut(self):
 7         ''' 找到最小st-剪切 '''
 8         X = [self.G.s] # st-剪切的X集合
 9         stack = [self.G.s]
10         while len(stack) > 0:
11             v = stack.pop()
12             for e in  self.G.edges_from(v): # 全部從v頂點流出的邊
13                 if e.w != self.G.t and e.w not in X and  e.residual_cap_to(e.w) > 0:
14                     X.append(e.w)
15                     stack.append(e.w)
16         Y = list(set(self.G.V) - set(X)) # st-剪切的X集合
17         st_cut = [e for e in self.G.E if e.v in X and e.w in Y] # 鏈接X和Y的邊
18         return X, Y, st_cut
19
20     def display_st_cut(self, X, Y, st_cut):
21         print('X={0}, Y={1}'.format(X, Y))
22         print('st-cut={}'.format([str(e) for e in st_cut]))

  min_st_cut使用深度優先搜索尋找最小st-剪切。因爲這種方法須要藉助殘存網,所以在使用min_st_cut前須要首先執行一次計算最大流的操做。如今能夠計算出乙軍的轟炸目標了:

姜子牙的押糧官

  在《封神演義》中,姜子牙通過金臺拜將以後,擔任「掃蕩成湯天寶大元帥」一職,代武王伐紂。率領六十萬西岐大軍浩浩蕩蕩,東進朝歌。所謂「三軍未動,糧草先行」,在臨行前,姜子牙任命了四個先鋒官的同時,又任命了楊戩、土行孫、鄭倫三個押糧官。

  押糧前必先徵糧,單從一個地方徵糧恐怕不足以支持一場滅國戰爭,假設下圖是西岐的糧道。

  邊的容量表明糧道的運力,楊戩、土行孫、鄭倫分別從三v1v2v3個徵糧地同時出發,將糧草運往惟一的前沿陣地t,因爲運糧過程當中十分安全,因此三人能夠在每一個節點處合兵或分兵,怎樣行進才能使糧道發揮出最大運力呢?

多個源點與多個匯點

  問題能夠規約成典型的最大流問題,但與以前介紹的st-網絡不一樣,糧道圖中有多個源點(或者說沒有源點),如此一來將會對最大流的相關算法形成影響,怎麼辦呢?

  其實很簡單,在三個源點前再加入一個超級源點,這樣一來v1v2v3就變成了普通的節點,它們也符合守恆定律,原網絡也轉換成了st-網:

  與此相似,也能夠經過創建一個超級匯點來處理多個匯點的狀況。

糧道的最大運力

  有了超級節點後,只要把初始數據輸入交給計算機就能夠了。

 1 import network_flow as nf
 2
 3 V = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
 4 E = [nf.Edge(0, 1, 18), nf.Edge(0, 2, 18), nf.Edge(0, 3, 18), nf.Edge(1, 4, 9),
 5      nf.Edge(1, 5, 9), nf.Edge(2, 5, 9), nf.Edge(2, 6, 9), nf.Edge(3, 6, 9),
 6      nf.Edge(3, 7, 9), nf.Edge(4, 8, 3), nf.Edge(4, 9, 12), nf.Edge(5, 9, 6),
 7      nf.Edge(5, 10, 14), nf.Edge(6, 10, 7), nf.Edge(6, 11, 5), nf.Edge(7, 11, 10),
 8      nf.Edge(7, 12, 12), nf.Edge(8, 13, 8), nf.Edge(9, 13, 8), nf.Edge(10, 13, 8),
 9      nf.Edge(11, 13, 8), nf.Edge(12, 13, 8)]
10 s, t = 0, 13
11 G = nf.Network(V, E, s, t)
12 ford_fullkerson = nf.FordFulkerson(G)
13 ford_fullkerson.start()
14 ford_fullkerson.display()

  最大流是33,下圖是根據程序運行結果映射的網絡流。

  最大流是肯定的,但押糧路線並非惟一的,最大流算法和邊的輸入順序都會對它產生影響。對於增廣路徑最大流算法來講,尋找增廣路徑的算法也會影響最終的押糧路線。

  


   做者:我是8位的

  出處:http://www.cnblogs.com/bigmonkey

  本文以學習、研究和分享爲主,如需轉載,請聯繫本人,標明做者和出處,非商業用途! 

  掃描二維碼關注公衆號「我是8位的」

相關文章
相關標籤/搜索