圖論1

存圖資料結構、DFS/BFS、MST

什麼是圖論?

圖論aka

對圖的拓樸化研究

就是一堆點跟線啦

柯尼斯堡七橋問題

對圖的拓樸研究

aka

將圖簡化為點跟線

什麼是圖

圖的定義

點加上線

 

或嚴格來說

點的集合加上邊的集合

\(G = (V, E)\)

圖的術語

圖上一個個點

圖上一個個邊

 

接下來稍微難個一點點

邊的方向性

對於有些邊

我們可能會限定只能從其中一端走向另一端

我們將這種邊稱為-有向邊

點度(degree)

代表這個點連結了幾條邊

對於有向圖會再進一步分類

  • 入度:代表通往這個點的邊數
  • 出度:代表離開這個點的邊數

路徑 (path)

  • 一組邊
  • 每條邊的終點是下條邊的起點
  • 起終點相同的路徑稱為環(loop)
  • 有時會強調簡單路徑,代表沒有經過重複的點/邊
  • 簡單環同理

點間的關係

  • 相鄰:兩點直接相接
  • 連通:兩點間存在路徑

A

C

B

\((A,B),(B,C)\)相鄰且連通

\((A,C)\)間則只有聯通

特別的圖

連通圖

對於圖中的任一點

都與任意其他一點間存在路徑

邊的數量為 \(V-1\) 的連通圖會是一棵樹

有時我們會將任意一個點定為根

即稱為有根樹

像接下來這樣

現實生活中的樹

資訊領域中的樹

實際上,他長這樣

A

B

C

F

G

H

E

D

根節點

葉節點

父節點

子節點

完全圖

圖中任意兩點都存在邊

二分圖

可以被切為兩個不相連的點集

有向無環圖

顧名思義,沒有環的有向圖

這類圖可以進行拓樸排序(Topological Sort)

1

5

4

2

3

圖的資料結構

相鄰矩陣

用一個二維矩陣存取每一個點間是否相鄰及邊權

相鄰串列

用\(V\)個串列去存取每個點與哪些邊相鄰

 相鄰矩陣

開一個\(V * V\)的二維陣列

陣列的\(i,j\)項表示

\(i\)點與\(j\)點是否相鄰

或者也可以存取邊權

範例

X 1 2 3 4 5 6
1 0 0 0 1 1 0
2 0 0 0 1 0 0
3 0 0 0 0 1 1
4 1 1 0 0 1 1
5 1 0 1 1 0 0
6 0 0 1 1 0 0

1

2

3

4

5

6

無向圖中會左下右上對稱!

相鄰串列

開\(V\)個串列

對於第\(i\)個串列,存\(i\)點相鄰的邊

範例

1 4,5
2 4
3 5,6
4 1,2,5,6
5 3,4
6 3,4

1

2

3

4

5

6

空間複雜度比較

相鄰矩陣是\(O(V^2)\)

相鄰串列是\(O(E)\)

相鄰串列的空間複雜度比較好

時間

以時間來說,可以分為遍歷圖和判斷兩點是否相鄰

以遍歷圖來說

矩陣是\(O(V^2)\)

串列是\(O(E)\)

那判斷呢?

矩陣:\(O(1)\) , 串列:\(O(V)\)

圖的遍歷

深度優先搜尋

也就是每次朝最深的那個做

每次遍歷到一個點就繼續遍歷相鄰的所有點

*但要注意不能走到已經遍歷過的點!

DFS範例

1

2

3

4

5

6

從點1開始遞迴

1-4-2-5-3-6

Example Code

//還記得前面建圖的函數ㄇ,繼續下去喔
bool visited[maxn];

void dfs(int v){
    visited[v] = true; //表示點v已被造訪
    /*
    	做你在遍歷時想做的事
    */
    for (auto i : graph[v]){
        if (visited[i]) //略過已造訪過的點
            continue;
        dfs(i);
    }
}

廣度優先搜尋

相對於深度優先

廣度優先會優先將深度相同的點遍歷完

再去遍歷下一組深度的點

*一樣要注意不要遍歷到已被遍歷的點

透過queue維護

BFS範例

1

2

3

4

5

6

by深度

1

4 5

2 6 3

注意

維護時要注意不要將已經被遍歷過的點加入queue中

否則空間複雜度會變成\(O(V+E)\)而不是\(O(V)\)

而一被加入queue中即可視為已被遍歷了

Code Example

//一樣接著前面喔
#include <bitset>
#include <queue>

void bfs(int startVercile){
    queue <int> toBFS;
    bitset <MAXN> visited;
    toBFS.push(startVercile);
    visited[toBFS.front()] = true;

    while (toBFS.size()){ //當queue內尚有元素時執行
        /*
            做你在遍歷時想做的事
        */
        for (auto i : graph[toBFS.front()]){
            if (!visited[i.first]) //如果該點尚未被造訪則放入queue中
                toBFS.push(i.first);
        }
    }
}

MST

What is MST

Minimum/Maximum Spanning Tree

最小/大棵的生成樹

那要怎麼做出這棵樹呢

生成樹?

生成樹指的是

對於一張圖,我們只其中取一些邊使所有節點都相連

而且這些部份邊必須形成一棵樹

先討論性質!

如果一棵生成樹的邊權和最小,那它有什麼性質?

Hint:先用直覺想一下,不要管證明

邊權越小的邊

我們越希望它被加入最小生成樹

Ans點我

我們獲得了貪婪原則!

Prim's Algorithm

Prim's Algorithm

  1. 先隨意挑選一個起點
  2. 將加入的點連向MST外的邊加入考慮範圍
  3. 從考慮範圍中取連外邊中邊權最小的邊加入MST
  4. 重複2~3,直到有\(V-1\)條邊為止

還記得剛剛那張圖吧

我們加上邊權

1

2

3

4

5

6

7

2

1

3

3

2

2

試試看

從點1開始

1

2

3

4

5

6

7

2

1

3

3

2

2

取邊\((1,4),w = 2\)

取邊\((4,5),w = 1\)

取邊\((5,3),w = 2\)

取邊\((3,6),w = 2\)

取邊\((2,4),w = 7\)

複雜度多少?

由於要維護一個持續取最小值的集合

因此用priority queue去維護

而需要對priority queue操作\(V+E\)次

每次操作耗時\(log(E)\)

因此總複雜度為\(O((V+E)log(E))\)

Kruskal's Algorithm

另一種貪婪法

如果現在給你兩棵MST

要將這兩棵樹連起來

你會選擇哪條邊?

邊權最小的那條邊R

Ans點我

Kruskal's Algorithm

  1. 依邊權排序邊
  2. 每次取邊權最小的邊
  3. 若連結兩棵MST,就將MST合併
  4. 重複2~3,直到有\(V-1\)條邊

合併?

DSU

並查集Disjoint-Set Union

  • 近乎\(O(1)\)查詢
  • 近乎\(O(1)\)合併

How?

首先,將所有元素視為獨立集合

且每個集合的最小值為"代表項"

並且每個元素都有一個父元素

代表項的父元素定為本身

只要對代表項做事就能合併與判斷

DSU怎麼做

合併3,9

1 2 3 4 5 6 7 8 9 10

1

2

3

4

5

6

7

8

9

10

3

合併4,5

4

合併3,5

3

先查代表項再合併!

要查詢一個點的代表項,只要一直往父元素查詢就好了

複雜度好像沒有很好?

如果我們每次合併\((1,2),(2,3),(3,4)...(n-1,n)\)

此時查詢第\(n\)個點的複雜度會是\(O(n)\)

 

於是我們需要一個酷酷的東西

路徑壓縮!!

路徑壓縮

在查詢時,一路遞迴上去時

把經過每一項的父元素都直接設為代表項

這樣之後查詢就會是\(O(1)\)

(實際上均攤複雜度會落在\(O(\alpha(n))\)

但\(\alpha\)函數在正常數字範圍內都小於4

因此基本上可以視為\(O(1)\))

範例程式碼

//DSUUUUUUUUUUU
int dsu[MAXN]; //第i項表示i點的父節點

void Init(){
    for (int i=0;i<MAXN;++i)
        dsu[i] = i;
}
//先將每項的父元素設為本身

int query(int k){
    if (k == dsu[k])
        return k;
    
    dsu[k] = query(dsu[k]);
    return dsu[k];
}

void modify (int x,int y){
    int xAncestor = query(x);
    int yAncestor = query(y);

    if (xAncestor != yAncestor){
        dsu[xAncestor] = yAncestor;
    }
}

回到MST

再用kruskal找找看

先對邊以邊權sort

1

2

3

4

5

6

7

2

1

3

3

2

2

檢查\((4,5),w = 1\),放入

檢查\((1,4),w = 2\),放入

檢查\((3,5),w = 2\),放入

檢查\((3,6),w = 2\),放入

檢查\((1,5),w = 3\),不放入

檢查\((4,6),w = 3\),不放入

檢查\((2,4),w = 7\),放入

得到MST了!

複雜度評估

排序的複雜度會落在\(O(ElogE)\)

而接下來會做\(E\)次判斷

因此複雜度取較大的\(O(ElogE)\)

例題們

健康快樂又好玩ㄉ例題們

給你一張圖,求最小生成樹權重和,圖不連通則輸出-1

給你一張連通圖,邊權均為0或1

問你是否存在邊權和為\(k\)的生成樹

 

\(V \leq 10^5, E \leq 10^5,k \leq V-1\)

在三維坐標系上有\(n\)個點,在兩點間建立路徑的花費為距離的平方,求使所有點連通的最小花費

\(n < 5000\)

圖論1

By yungyao

圖論1

  • 501