圖算法

image.png

1. 圖

1.1. 概念

  • 頂點的度 d
  • 相鄰
  • 重邊
  • 完全圖: 所有頂都相鄰
  • 二分圖: V(G) = X \cup Y, X\cap Y = \varnothing, X中, Y 中任兩頂不相鄰
  • 軌道

1.1.1. 性質

  • \sum_{v\in V} d(v) = 2|E|
  • G是二分圖 \Leftrightarrow G無奇圈
  • 樹是無圈連通圖
  • 樹中, |E| = |V| -1

1.2. 圖的表示

  • 鄰接矩陣
  • 鄰接鏈表


1.3. 樹

無圈連通圖, E = V-1, 詳細見,

2. 搜索--求圖的生成樹[1]

2.1. BFS

for v in V:
    v.d = MAX
    v.pre = None
    v.isFind = False
root. isFind = True
root.d = 0
que = [root]
while que !=[]:
    nd = que.pop(0)
    for v in Adj(nd):
        if not v.isFind :
            v.d = nd.d+1
            v.pre = nd
            v.isFind = True
            que.append(v)

時間復雜度 O(V+E)

2.2. DFS

\Theta(V+E)

def dfs(G):
    time = 0
    for v in V:
        v.pre = None
        v.isFind = False
    for v in V : # note this, 
        if not v.isFind:
            dfsVisit(v)
    def dfsVisit(G,u):
        time =time+1
        u.begin = time
        u.isFind = True
        for v in Adj(u):
            if not v.isFind:
                v.pre = u
                dfsVisit(G,v)
        time +=1
        u.end = time  

begin, end 分別是結點的發現時間與完成時間

2.2.1. DFS 的性質

  • 其生成的前驅子圖G_{pre} 形成一個由多棵樹構成的森林, 這是因為其與 dfsVisit 的遞歸調用樹相對應
  • 括號化結構


  • 括號化定理:
    考察兩個結點的發現時間與結束時間的區間 [u,begin,u.end] 與 [v.begin,v.end]
    • 如果兩者沒有交集, 則兩個結點在兩個不同的子樹上(遞歸樹)
    • 如果 u 的區間包含在 v 的區間, 則 u 是v 的后代

2.3. 拓撲排序

利用 DFS, 結點的完成時間的逆序就是拓撲排序

同一個圖可能有不同的拓撲排序

2.4. 強連通分量

在有向圖中, 強連通分量中的結點互達
定義 GrevG 中所有邊反向后的圖

將圖分解成強連通分量的算法
在 Grev 上根據 G 中結點的拓撲排序來 dfsVisit, 即

compute Grev
initalization
for v in topo-sort(G.V):
    if not v.isFind: dfsVisit(Grev,v)

然后得到的DFS 森林(也是遞歸樹森林)中每個樹就是一個強連通分量

3. 最小生成樹

利用了貪心算法,

3.1. Kruskal 算法

總體上, 從最開始 每個結點就是一顆樹的森林中(不相交集合, 并查集), 逐漸添加不形成圈的(兩個元素不再同一個集合),最小邊權的邊.

edges=[]
for  edge as u,v in sorted(G.E):
    if find-set(u) != find-set(v):
        edges.append(edge)
        union(u,v)
return edges

如果并查集的實現采用了 按秩合并與路徑壓縮技巧, 則 find 與 union 的時間接近常數
所以時間復雜度在于排序邊, 即 O(ElgE), 而 E<V^2, 所以 lgE = O(lgV), 時間復雜度為 O(ElgV)

3.2. Prim 算法

用了 BFS, 類似 Dijkstra 算法
從根結點開始 BFS, 一直保持成一顆樹

for v in V: 
    v.minAdjEdge = MAX
    v.pre = None
root.minAdjEdge = 0
que = priority-queue (G.V)  # sort by minAdjEdge
while not que.isempty():
    u = que.extractMin()
    for v in Adj(u):
        if v in que and v.minAdjEdge>w(u,v):
            v.pre = u
            v.minAdjEdge = w(u,v)
  • 建堆 O(V) //note it's v, not vlgv
  • 主循環中
    • extractMin: O(VlgV)
    • in 操作 可以另設標志位, 在常數時間完成, 總共 O(E)
    • 設置結點的 minAdjEdge, 需要O(lgv), 循環 E 次,則 總共O(ElgV)

綜上, 時間復雜度為O(ElgV)
如果使用的是 斐波那契堆, 在 設置 minAdjEdge時 調用 decrease-key, 這個操作攤還代價為 O(1), 所以時間復雜度可改進到 O(E+VlgV)

4. 單源最短路

求一個結點到其他結點的最短路徑, 可以用 Bellman-ford算法, 或者 Dijkstra算法.
定義兩個結點u,v間的最短路
\delta(u,v) = \begin{cases} \min(w(path)),\quad u\xrightarrow{path} v\\ \infty, \quad u\nrightarrow v \end{cases}
問題的變體

  • 單目的地最短路問題: 可以將所有邊反向轉換成求單源最短路問題
  • 單結點對的最短路徑
  • 所有結點對最短路路徑

最短路的子路徑也是最短路徑

p=(v_0,v_1,\ldots,v_k)為從結點v_0v_k的一條最短路徑, 對于任意0\le i\le j \le k, 記p_{ij}=(v_i,v_{i+1},\ldots,v_j)為 p 中 v_iv_j的子路徑, 則 p_{ij}v_iv_j的一條最短路徑

4.1. 負權重的邊

Dijkstra 算法不能處理, 只能用 Bellman-Ford 算法,
而且如果有負值圈, 則沒有最短路, bellman-ford算法也可以檢測出來

4.2. 初始化

def initialaize(G,s):
    for v in G.V:
        v.pre = None
        v.distance = MAX
    s.distance = 0

4.3. 松弛操作

def relax(u,v,w):
    if v.distance > u.distance + w:
        v.distance = u.distance + w:
         v.pre = u

性質

  • 三角不等式: \delta(s,v) \leqslant \delta(s,u) + w(u,v)
  • 上界: v.distance \geqslant \delta(s,v)
  • 收斂: 對于某些結點u,v 如果s->...->u->v是圖G中的一條最短路徑,并且在對邊,進行松弛前任意時間有 u.distance=\delta(s,u)則在之后的所有時間有 v.distance=\delta(s,v)
  • 路徑松弛性質: 如果p=v_0 v_1 \ldots v_k是從源結點下v0到結點vk的一條最短路徑,并且對p中的邊所進行松弛的次序為(v_0,v_1),(v_1,v_2), \ldots ,(v_{k-1},v_k), 則 v_k.distance = \delta(s,v_k)
    該性質的成立與任何其他的松弛操作無關,即使這些松弛操作是與對p上的邊所進行的松弛操作穿插進行的。

證明


4.4. 有向無環圖的單源最短路問題

\Theta(V+E)

def dag-shortest-path(G,s):
    initialize(G,s)
    for u in topo-sort(G.V):
        for v in Adj(v):
            relax(u,v,w(u,v))

4.5. Bellman-Ford 算法

O(VE)

def bellman-ford(G,s):
    initialize(G,s)
    for ct in range(|V|-1): # v-1 times
        for u,v as edge in E:
            relax(u,v,w(u,v))
    for u,v as edge in E:
        if v.distance > u.distance + w(u,v):
            return False
    return True

第一個 for 循環就是進行松弛操作, 最后結果已經存儲在 結點的distance 和 pre 屬性中了, 第二個 for 循環利用三角不等式檢查有不有負值圈.

下面是證明該算法的正確性

4.6. Dijkstra 算法

O(ElogV), 要求不能有負值邊

Dijkstra算法既類似于廣度優先搜索(,也有點類似于計算最小生成樹的Prim算法。它與廣度優先搜索的類似點在于集合S對應的是廣度優先搜索中的黑色結點集合:正如集合S中的結點的最短路徑權重已經計算出來一樣,在廣度優先搜索中,黑色結點的正確的廣度優先距離也已經計算出來。Dijkstra算法像Prim算法的地方是,兩個算法都使用最小優先隊列來尋找給定集合(Dijkstra算法中的S集合與Prim算法中逐步增長的樹)之外的“最輕”結點,將該結點加入到集合里,并對位于集合外面的結點的權重進行相應調整。

def dijkstra(G,s):
    initialize(G,s)
    paths=[]
    q = priority-queue(G.V) # sort by distance
    while not q.empty():
        u = q.extract-min()
        paths.append(u)
        for v in Adj(u):
            relax(u,v,w(u,v))

5. 所有結點對的最短路問題

5.1. 矩陣乘法

使用動態規劃算法, 可以得到最短路徑的結構
l_{ij}^{(m)}為從結點i 到結點 j 的至多包含 m 條邊的任意路徑的最小權重,當m = 0, 此時i=j, 則 為0,
可以得到遞歸定義
l_{ij}^{(m)} =\min( l_{ij}^{(m-1)}, \min_{1\leqslant k\leqslant n}( l_{ik}^{(m-1)}+w_{kj})) = \min_{1\leqslant k\leqslant n}( l_{ik}^{(m-1)}+w_{kj}))
由于對于所有 j, 有 w_{jj}=0,所以上式后面的等式成立.

由于是簡單路徑, 則包含的邊最多為 |V|-1 條, 所以
\delta(i,j) = l_{ij}^{(|V|-1)} = l_{ij}^{(|V|)} =l_{ij}^{(|V| + 1)}= ...
所以可以從自底向上計算, 如下
輸入權值矩陣 W(w_{ij})), L^{(m-1)},輸出L^{(m)}, 其中 L^{(1)} = W,

def  f(L, W):
  n = L.rows
  L_new = new matrix(row=n ,col = n)
  for i in range(n):
      for j in range(n):
          L_new[i][j] = MAX
          for k in range(n):
              L_new[i][j] = min(L_new[i][j], L[i][k]+w[k][j])
  return L_new

可以看出該算法與矩陣乘法的關系
L^{(m)} = W^m,
所以可以直接計算乘法, 每次計算一個乘積是 O(V^3), 計算 V 次, 所以總體 O(V^4), 使用矩陣快速冪可以將時間復雜度降低為O(V^3lgV)

def f(W):
    L = W
    i = 1
    while i<W.rows:
        L = L*L
        i*=2
    return L

5.2. Floyd-Warshall 算法

同樣要求可以存在負權邊, 但不能有負值圈. 用動態規劃算法:
d_{ij}^{(k)} 為 從 i 到 j 所有中間結點來自集合 {\{1,2,\ldots,k\}} 的一條最短路徑的權重. 則有
d_{ij}^{(k)} = \begin{cases} w_{ij},\quad k=0\\ min(d_{ij}^{(k-1)},d_{ik}^{(k-1)}+d_{kj}^{(k-1)}),\quad k\geqslant 1 \end{cases}
而且為了找出路徑, 需要記錄前驅結點, 定義如下前驅矩陣 \Pi, 設 \pi_{ij}^{(k)} 為 從 i 到 j 所有中間結點來自集合 {\{1,2,\ldots,k\}} 的最短路徑上 j 的前驅結點

\pi_{ij}^{(0)} = \begin{cases} nil,\quad i=j \ or \ w_{ij}=\infty \\ i, \quad i\neq j\ and \ w_{ij}<\infty \end{cases}
k\geqslant 1
\pi_{ij}^{(k)} = \begin{cases} \pi_{ij}^{(k-1)} ,\quad d_{ij}^{(k-1)}\leqslant d_{ik}^{(k-1)}+d_{kj}^{(k-1)}\\ \pi_{kj}^{(k-1)} ,\quad otherwise \end{cases}

由此得出此算法

def floyd-warshall(W):
    n = len(W)
    D= W
    initialize pre
    for k in range(n):
        pre2 = pre.copy()
        for i in range(n):
            for j in range(n)
                if d[i][j] > d[i][k]+d[k][j]:
                    d[i][j] =d[i][k]+d[k][j]
                    pre2[i][j] = pre[k][j]
        pre = pre2
return d,pre

5.3. Johnson 算法

思路是通過重新賦予權重, 將圖中負權邊轉換為正權,然后就可以用 dijkstra 算法(要求是正值邊)來計算一個結點到其他所有結點的, 然后對所有結點用dijkstra

  1. 首先構造一個新圖 G'
    先將G拷貝到G', 再添加一個新結點 s, 添加 G.V條邊, s 到G中頂點的, 權賦值為 0
  2. 用 Bellman-Ford 算法檢查是否有負值圈, 如果沒有, 同時求出 \delta(s,v) 記為 h(v)
  3. 求新的非負值權, w'(u,v) = w(u,v)+h(u)-h(v)
  4. 對所有結點在 新的權矩陣w'上 用 Dijkstra 算法


    image.png
JOHNSON (G, u) 

s = newNode
G' = G.copy()
G'.addNode(s)
for v in G.V: G'.addArc(s,v,w=0)

if BELLMAN-FORD(G' , w, s) ==FALSE 
    error "the input graph contains a negative-weight cycle" 

for v in G'.V:
    # computed by the bellman-ford algorithm, delta(s,v) is the shortest distance from s to v
    h(v) = delta(s,v) 
for edge(u,v) in G'.E:
    w' = w(u,v)+h(u)-h(v)
d = matrix(n,n)
for u in G:
    dijkstra(G,w',u) # compute delta' for all v in G.V
    for v in G.V:
        d[u][v] = delta'(u,v) + h(v)-h(u)
return d

6. 最大流

G 是弱連通嚴格有向加權圖, s為源, t 為匯, 每條邊e容量 c(e), 由此定義了網絡N(G,s,t,c(e)),

  • 流函數 f(e):E \rightarrow R
    \begin{aligned} (1)\quad & 0\leqslant f(e) \leqslant c(e),\quad e \in E\\ (2)\quad & \sum_{e\in \alpha(v)} f(e)= \sum_{e\in \beta(v)}f(e),\quad v \in V-\{s,t\} \end{aligned}
    其中 \alpha(v) 是以 v 為頭的邊集, \beta(v)是以 v 為尾的邊集
  • 流量: F = \sum_{e\in \alpha(t)} f(e)- \sum_{e\in -\beta(t)}f(e),
  • (S,\overline S): S\subset V,s\in S, t\in \overline S =V-S
  • 截量C(S) = \sum_{e\in(S,\overline S)}c(e)

6.1. 定理[2]

  • 對于任一截(S,\overline S), 有 F = \sum_{e\in (S,\overline S)} f(e)- \sum_{e\in(\overline S,S)}f(e),
    prove
  • F\leqslant C(S)
    證明: 由上面定理
    F = \sum_{e\in (S,\overline S)} f(e)- \sum_{e\in(\overline S,S)}f(e),
    0\leqslant f(e) \leqslant c(e), 則
    F\leqslant \sum_{e\in (S,\overline S)} f(e) \leqslant \sum_{e\in (S,\overline S)} c(e) = C(S)
  • 最大流,最小截: 若F= C(S), 則F'是最大流量, C(S) 是最小截量

6.2. 多個源,匯

可以新增一個總的源,一個總的匯,


6.3. Ford-Fulkerson 方法

由于其實現可以有不同的運行時間, 所以稱其為方法, 而不是算法.
思路是 循環增加流的值, 在一個關聯的"殘存網絡" 中尋找一條"增廣路徑", 然后對這些邊進行修改流量. 重復直至殘存網絡上不再存在增高路徑為止.

def ford-fulkerson(G,s,t):
    initialize flow f to 0
    while exists an augmenting path p in residual network Gf:
        augment flow f along p
    return f

6.3.1. 殘存網絡

6.3.2. 增廣路徑

6.3.3. 割

6.4. 基本的 Ford-Fulkerson算法

def ford-fulkerson(G,s,t):
    for edge in G.E: edge.f = 0
    while exists path p:s->t  in Gf:
        cf(p) = min{cf(u,v):(u,v) is in p}
        for edge in p:
            if edge  in E:
                edge.f +=cf(p)
            else: reverse_edge.f -=cf(p)

6.5. TBD

7. 參考資料


  1. 算法導論 ?

  2. 圖論, 王樹禾 ?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容