Graph[1]

algorithm[12] 22527 Brine

Index[0]
Index[1]
Disjoint Set

並查集

查詢和合併連通分量

  • 今天我希望有一個資料結構,支援以下操作:
    • 查詢元素 \(e\) 所在的集合
    • 合併兩個給定的集合
  • 假設今天這是一個圖論的問題,那上述操作即為:
    • 查詢點 \(v\) 所在的連通分量
    • 將兩連通分量連起來

怎麼做?

  • 查詢時,回傳該集合「代表元素」的編號
  • 只要找到代表元素就回傳

 

  • 令 \(q(i)\) 回傳元素 \(i\) 所在集合的代表元素
  • 假設一開始所有元素都是獨自形成一個集合,就是 \(q(i) = i\)

修改   的定義

  • 既然我們剛剛說初始條件 \(q(i) = i\),那不如我們試試看照著做
    • 如果要「確定」找到的代表元素正確(唯一)要怎麼做
      • 直到找到自己的代表元素為自己為止
q(i)
vector<int> master(vertexCount);

int query(int i) {
    if (master[i] == i) return i;
    
    return query(master[i]);
}

合併集合

  • 看起來是什麼樣子?
0
1
3
2
4

合併集合

  • 看起來是什麼樣子?
0
1
3
2
4
  • 合併只要 \(\mathcal O(1)\)!
    • 那查詢呢?

鍊狀的情況

  • 如果照我們的想法,但合併時變出一條鍊會怎樣?
0
1
3
2
4

鍊狀的情況

  • 如果照我們的想法,但合併時變出一條鍊會怎樣?
0
1
3
2
4
  • \(\mathcal O(n)\) 查詢,不太妙
    • 怎麼辦?

啟發式合併

  • 我們想要使樹的高度(深度)越低越好
  • 可以從合併的方法下手!
    • 如果我們開個紀錄「自己是根時樹的高度」的陣列呢
      • 合併時比較兩棵樹的高度
      • 將比較矮的接到比較高的下面
      • 這樣有多好?
    • 複雜度長怎樣
      • 讓最大樹高變成 \(h\) 至少需要 \(2^h-1\) 次合併
        • 樹高不可能超過 \(\lceil \log|V| \rceil\)!
      • 查詢的複雜度現在只剩 \(\mathcal O(\log |V|)\) 了

複雜度分析

  • 是不是有人看不懂剛剛在幹嘛
0
1
2
3

複雜度分析

  • 是不是有人看不懂剛剛在幹嘛
0
1
3
2

複雜度分析

  • 是不是有人看不懂剛剛在幹嘛
0
1
3
2
4

複雜度分析

  • 是不是有人看不懂剛剛在幹嘛
0
1
3
2
4

複雜度分析

  • 是不是有人看不懂剛剛在幹嘛
0
1
3
2
4
5
7
6
8

複雜度分析

  • 是不是有人看不懂剛剛在幹嘛
0
1
3
2
4
5
7
6
8

複雜度分析

  • 是不是有人看不懂剛剛在幹嘛
0
1
3
2
4
5
7
6
8
  • 需要兩個高度為 \(h\) 的樹合併,高度才會增加 1!
  • 任何兩不同高度的樹合併都不增加最大深度
  • \(V\) 合併的深度屬於 \(\mathcal O(\log|V|)\)

路徑壓縮

  • \(q(i)\) 現在是「找到代表元素為自己的元素後回傳」
    • 那我們能不能在遞迴時順便做些事情呢?
  • 在查詢時順便把路徑上每個元素的代表元素改成終點聽起來不錯吧
int query(int i) {
    return (master[i] == i ? i : query(master[i]));
}
int query(int i) {
    if (master[i] == i) return i;
    
    master[i] = query(master[i]);
    return master[i];
}

圖例

  • 剛才那樣有點抽象對吧
0
1
3
2
4
5

圖例

  • 剛才那樣有點抽象對吧
0
1
3
2
4
5

圖例

  • 剛才那樣有點抽象對吧
0
1
3
2
4
5

圖例

  • 剛才那樣有點抽象對吧
0
1
3
2
4
5

圖例

  • 剛才那樣有點抽象對吧
0
1
3
2
4
5

圖例

  • 剛才那樣有點抽象對吧
0
1
3
2
4
5

圖例

  • 剛才那樣有點抽象對吧
0
1
3
2
4
5

圖例

  • 剛才那樣有點抽象對吧
0
1
3
2
4
5

圖例

  • 剛才那樣有點抽象對吧
0
1
3
2
4
5
  • 查詢時每次多一個修改的動作,但以後查詢變輕鬆了!

快,還要更快

  • 光是剛才的路徑壓縮,就快到不行了
    • 查詢複雜度為 \(\mathcal O(\log_{(2 + \frac{Q}{|V|})}|V|)\)
    • 我不會證明
  • 但其實還可以更快?!
    • 記得剛才的「啟發式合併」嗎?
    • 配合路徑壓縮的話複雜度可以再更低
    • 啟發式合併 + 路徑壓縮的複雜度只有 \(\mathcal O(\alpha(|V|))\)

Disjoint Set code

  • 只寫路徑壓縮通常就夠快了啦
struct DisjointSet {
    vector<int> master, depth;

    disjointSet(int vertexCount) {
        master.resize(vertexCount);
        depth.resize(vertexCount);

        for (int i = 0; i < vertexCount; i++) {
            master[i] = i;
            depth[i] = 1;
        }
    }

    int query(int i) {
        if (master[i] == i) return i;

        return master[i] = query(master[i]);
    }

    void merge(int a, int b) {
        a = query(a);
        b = query(b);

        if (a == b) return;

        if (depth[a] < depth[b]) swap(a, b);
        master[b] = a;
        depth[a] += depth[b];
    }
}; // 記得分號

例題

Spanning Tree

生成樹

邊權和最小的連通子圖

  • 今天有一張連通圖 \(G(V, E)\),我們想要一張子圖 \(G'(V', E')\) 符合:
    • 點集 \(V = V'\)
    • 邊權和 \(\displaystyle \sum_{e \in E'} w_e\) 最小
    • \(G'\) 仍然連通
  • 在正常情況下(\(\forall e \in E, w_e > 0\)),該圖一定是一棵樹
    • 我們稱這棵樹為最小生成樹,Minimum Spanning Tree,MST
    • 什麼情況下可以不是樹?
    • 最小生成樹唯一嗎

Cycle Property

  • 環性質
  • 對於任何圖上的環 \(C\),邊權嚴格最大的邊 \(e\) 不屬於最小生成樹
  • 用反證法來證明此性質:
    • 假設 \(e\) 屬於最小生成樹
    • 刪除 \(e\) 會使最小生成樹分裂成兩個子樹
    • 但環 \(C\) 上剩下的邊必定存在一個 \(e'\) 能連接兩個子樹
    • \(w_e > w_{e'}\),此 MST 並非最小權重,矛盾!
  • 這個可以拿來幹嘛?
    • 等一下就知道了
0
5
1
4
2
6
3

Cut property

  • 🈹性質
  • 什麼是割?

Cut property

  • 🈹性質
  • 什麼是割?
0
1
3
2
4

Cut property

  • 🈹性質
  • 什麼是割?
0
1
3
2
4

Cut property

  • 🈹性質
  • 什麼是割?
0
1
3
2
4

Cut property

  • 🈹性質
  • 什麼是割?
0
1
3
2
4

Cut property

  • 🈹性質
  • 什麼是割?
    • 把一張圖的點分割為兩子集
    • 一個割會形成一個割集(cut-set)
    • 割集為跨越兩子點集的邊集
  • 割集中權重嚴格最小的邊屬於 MST
    • 跟剛才一樣,反證法
    • 若它不屬於,換成它一定更好
0
1
3
2
4

不嚴格怎麼辦

0
5
1
4
2
6
3
7
7
1
1
5
3
4
9
1
2
  • 如果剛才環或是割中,沒有邊權嚴格大於/小於所有邊的邊呢
    • 這個時候就是「平手」,選誰都一樣
    • 對於 cycle property,就是兩個邊都一定不會存在於某個 MST
      • 言下之意就是,會有超過一個 MST 存在,而至少有一個沒有其中一個邊
    • 對於 cut property,就是兩個邊都一定能存在於某個 MST 中

不嚴格怎麼辦

0
5
1
4
2
6
3
7
7
1
1
5
3
4
9
1
2
  • 如果剛才環或是割中,沒有邊權嚴格大於/小於所有邊的邊呢
    • 這個時候就是「平手」,選誰都一樣
    • 對於 cycle property,就是兩個邊都一定不會存在於某個 MST
      • 言下之意就是,會有超過一個 MST 存在,而至少有一個沒有其中一個邊
    • 對於 cut property,就是兩個邊都一定能存在於某個 MST 中

不嚴格怎麼辦

  • 如果剛才環或是割中,沒有邊權嚴格大於/小於所有邊的邊呢
    • 這個時候就是「平手」,選誰都一樣
    • 對於 cycle property,就是兩個邊都一定不會存在於某個 MST
      • 言下之意就是,會有超過一個 MST 存在,而至少有一個沒有其中一個邊
    • 對於 cut property,就是兩個邊都一定能存在於某個 MST 中
0
5
1
4
2
6
3
7
7
1
1
5
3
4
9
1
2

不嚴格怎麼辦

  • 如果剛才環或是割中,沒有邊權嚴格大於/小於所有邊的邊呢
    • 這個時候就是「平手」,選誰都一樣
    • 對於 cycle property,就是兩個邊都一定不會存在於某個 MST
      • 言下之意就是,會有超過一個 MST 存在,而至少有一個沒有其中一個邊
    • 對於 cut property,就是兩個邊都一定能存在於某個 MST 中
0
5
1
4
2
6
3
7
7
1
1
5
3
4
9
1
2

不嚴格怎麼辦

  • 如果剛才環或是割中,沒有邊權嚴格大於/小於所有邊的邊呢
    • 這個時候就是「平手」,選誰都一樣
    • 對於 cycle property,就是兩個邊都一定不會存在於某個 MST
      • 言下之意就是,會有超過一個 MST 存在,而至少有一個沒有其中一個邊
    • 對於 cut property,就是兩個邊都一定能存在於某個 MST 中
0
5
1
4
2
6
3
7
7
1
1
5
3
4
9
1
2

不嚴格怎麼辦

  • 如果剛才環或是割中,沒有邊權嚴格大於/小於所有邊的邊呢
    • 這個時候就是「平手」,選誰都一樣
    • 對於 cycle property,就是兩個邊都一定不會存在於某個 MST
      • 言下之意就是,會有超過一個 MST 存在,而至少有一個沒有其中一個邊
    • 對於 cut property,就是兩個邊都一定能存在於某個 MST 中
0
5
1
4
2
6
3
7
7
1
1
5
3
4
9
1
2

不嚴格怎麼辦

  • 如果剛才環或是割中,沒有邊權嚴格大於/小於所有邊的邊呢
    • 這個時候就是「平手」,選誰都一樣
    • 對於 cycle property,就是兩個邊都一定不會存在於某個 MST
      • 言下之意就是,會有超過一個 MST 存在,而至少有一個沒有其中一個邊
    • 對於 cut property,就是兩個邊都一定能存在於某個 MST 中
0
5
1
4
2
6
3
7
7
1
1
5
3
4
9
1
2

不嚴格怎麼辦

  • 如果剛才環或是割中,沒有邊權嚴格大於/小於所有邊的邊呢
    • 這個時候就是「平手」,選誰都一樣
    • 對於 cycle property,就是兩個邊都一定不會存在於某個 MST
      • 言下之意就是,會有超過一個 MST 存在,而至少有一個沒有其中一個邊
    • 對於 cut property,就是兩個邊都一定能存在於某個 MST 中
0
5
1
4
2
6
3
7
7
1
1
5
3
4
9
1
2
  • 從最小的邊開始,依次嘗試加入 MST
  • 如果加入後不會形成環,它就屬於 MST
  • 為什麼這是對的?
    • 分類討論!

加入邊後形成環

  • 我們剛才說「從邊權小到邊權大開始試」
    • 那最後一個加入的邊所形成的環中一定是它邊權最大
    • 根據 cycle property,它一定不屬於某個 MST
      • 既然它不屬於某一個,我們就讓它不屬於我們這個吧!

加入邊後不形成環

  • 加入邊後不形成環,代表它使兩個連通分量連通
    • 它連接了兩個被分割的點集
    • 根據剛才的「由小到大」,它是屬於某個割集中最小權者
    • 根據 cut property,它一定屬於某一個 MST
      • 既然它屬於某一個,我們就讓它屬於我們這個吧!

我們證明它是對的了!

  • 那要怎麼實作?
  • 我們需要一個可以「判斷兩點是否連通」的資料結構
  • 使用剛剛學到的 disjoint set!
  • 它的複雜度會是什麼呢?
    • 需要對邊排序,複雜度 \(\mathcal O(|E|\log|E|)\)
    • 使用並查集 \(|E|\) 次,複雜度 \(\mathcal O(|E|\cdot \alpha(|E|))\)
    • 總複雜度 \(\mathcal O(|E|\log|E|)\)

Kruskal Code

  • 先別急,等等還有另外一種方法
typedef pair<int, int> pii;
typedef pair<int, pii> pi_ii;

struct DisjointSet {
    vector<int> master;

    DisjointSet(int vertexCount) {
        master.resize(vertexCount);
        iota(master.begin(), master.end(), 0);
    }

    int query(int a) {
        if (a == master[a]) return a;

        return master[a] = query(master[a]);
    }

    bool connected(int a, int b) {
        return query(a) == query(b);
    }

    void merge(int a, int b) {
        master[query(a)] = master[b];
    }
};

int kruskal(vector<pi_ii>& minEdge, DisjointSet& ds) {
    int cost = 0;
    
    for (auto& [w, uv]: minEdge) {
        auto& [u, v] = uv;

        if (ds.connected(u, v)) continue;

        ds.merge(u, v);
        cost += w;
    }

    return cost;
}

int main() {
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    int vertexCount, edgeCount;
    cin >> vertexCount >> edgeCount;
    DisjointSet ds(vertexCount);

    vector<pi_ii> minEdge(edgeCount);
    for (auto& [w, uv]: minEdge) {
        auto& [u, v] = uv;
        cin >> u >> v >> w;
	}
    
    sort(minEdge.begin(), minEdge.end());

    cout << kruskal(minEdge, ds) << '\n';
}
  • 一般是稱為 Prim's algorithm,但是是 Jarník 先發現的
  • 步驟:
    1. 選擇任意一點開始
    2. 選擇目前點集連出去所有邊中最小者
    3. 將該邊的另外一端加入當前點集
    4. 重複直到所有點都被加入當前點集

為什麼是對的

  • 🈹性質!

為什麼是對的

  • 🈹性質!
8
1
3
2
7
5
6
0
4
9

為什麼是對的

  • 🈹性質!
8
1
3
2
7
5
6
0
4
9
13
2
2
5
2
7
9
2
6
8
2
3
4
7
1

為什麼是對的

  • 🈹性質!
8
1
3
2
7
5
6
0
4
9
13
2
2
5
2
7
9
2
6
8
2
3
4
7
1

為什麼是對的

  • 🈹性質!
8
1
3
2
7
5
6
0
4
9
13
2
2
5
2
7
9
2
6
8
2
3
4
7
1

為什麼是對的

  • 🈹性質!
8
1
3
2
7
5
6
0
4
9
13
2
2
5
2
7
9
2
6
8
2
3
4
7
1

為什麼是對的

  • 🈹性質!
8
1
3
2
7
5
6
0
4
9
13
2
2
5
2
7
9
2
6
8
2
3
4
7
1

為什麼是對的

  • 🈹性質!
8
1
3
2
7
5
6
0
4
9
13
2
2
5
2
7
9
2
6
8
2
3
4
7
1

為什麼是對的

  • 🈹性質!
8
1
3
2
7
5
6
0
4
9
13
2
2
5
2
7
9
2
6
8
2
3
4
7
1

為什麼是對的

  • 🈹性質!
8
1
3
2
7
5
6
0
4
9
13
2
2
5
2
7
9
2
6
8
2
3
4
7
1

為什麼是對的

  • 🈹性質!
8
1
3
2
7
5
6
0
4
9
13
2
2
5
2
7
9
2
6
8
2
3
4
7
1

為什麼是對的

  • 🈹性質!
8
1
3
2
7
5
6
0
4
9
13
2
2
5
2
7
9
2
6
8
2
3
4
7
1

為什麼是對的

  • 🈹性質!
8
1
3
2
7
5
6
0
4
9
13
2
2
5
2
7
9
2
6
8
2
3
4
7
1
  • 每次皆可視為以選取和未選取兩點集
    • 這個割的割集符合割性質!

Prim Code

  • 這樣的複雜度是 \(\mathcal O(|E|\log|E|)\)
  • 可以用 Fibonacci heap 做到 \(\mathcal O(|E| + |V|\log|V|)\)
typedef pair<int, int> pii;

int prim(vector< vector<pii> >& graph) {
    priority_queue<pii, vector<pii>, greater<pii> > pq;
    vector<int> currentMinCost(graph.size(), INT32_MAX);
    vector<bool> inside(graph.size(), false);
    int sum = 0;

    pq.push({0, 0});
    currentMinCost[0] = 0;

    while (!pq.empty()) {
        auto [w, u] = pq.top();
        pq.pop();

        if (inside[u]) continue;
        inside[u] = true;
        sum += w;

        for (auto& [w, v]: graph[u]) {
            if (!inside[v] && currentMinCost[v] > w) {
                currentMinCost[v] = w;
                pq.push({w, v});
            }
        }
    }

    return sum;
}

int main() {
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    int vertexCount, edgeCount;
    cin >> vertexCount >> edgeCount;

    vector< vector<pii> > graph(vertexCount);
    int u, v, w;
    for (int i = 0; i < edgeCount; i++) {
        cin >> u >> v >> w;
        graph[u].push_back({w, v});
        graph[v].push_back({w, u});
    }

    cout << prim(graph) << '\n';
}

例題

Shortest Path

最短路徑

最短路徑

  • 在生活中,我們常常遇到交通上的問題
  • 討論最短路徑的時候,會有不同的限制條件,例如:
    • 帶權/無權
    • 有負邊/無負邊
    • 有負環/無負環
    • 單點源/全點對
    • ……
  • 待會會介紹一些常見的最短路徑演算法

無權單點源最短路徑

  • 之前講過了!
  • 就是 BFS
  • 通常是類似走迷宮的題目
  • 如果是樹的話只要 DFS 就好
  • 複雜度 \(\mathcal O(|V| + |E|)\)

全點對最短路徑

  • 今天我們想要把整張圖的所有點對互相抵達的最短距離都算出來
    • 全點對最短路徑
  • 要怎麼做才好
  • 動態規劃!
    • 令 \(dp[k][i][j]\) 為使用前 \(k\) 個點中繼,從 \(i\) 到 \(j\) 的最短距離
    • \(dp[k+1][i][j] = min(dp[k][i][j], dp[k][i][k+1] + dp[k][k+1][j])\)
    • 可以滾動,所以 \(k\) 不用寫在狀態裡面
  • ​時間複雜度:\(\mathcal O(|V|^3)\)
  • 好寫,但不能處理負邊
vector< vector<int> > d(vC, vector<int>(vC, 1e9 + 225));
for (int i = 0; i < vC; i++) d[i][i] = 0;
int u, v, w;
for (int i = 0; i < eC; i++) {
    cin >> u >> v >> w;
    d[u][v] = min(d[u][v], w);
}

for (int k = 0; k < vC; k++) {
    for (int i = 0; i < vC; i++) {
        for (int j = 0; j < vC; j++) {
            d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
        }
    }
}

單點源最短路徑

  • 講到單點源(single-source)最短路徑,就一定要提到一個名詞
  • 鬆弛(relaxation)
    • 定義從起點 \(s\) 到點 \(i\) 的最短距離為 \(d_i\)
    • 有什麼條件時可以使 \(d_u\) 降低?
      • 存在一個邊 \(e = (u, v)\)
      • \(d_u > d_v + w_e\)

單點源最短路徑

s
v
u
2
25
  • 講到單點源(single-source)最短路徑,就一定要提到一個名詞
  • 鬆弛(relaxation)
    • 定義從起點 \(s\) 到點 \(i\) 的最短距離為 \(d_i\)
    • 有什麼條件時可以使 \(d_u\) 降低?
      • 存在一個邊 \(e = (u, v)\)
      • \(d_u > d_v + w_e\)

單點源最短路徑

  • 講到單點源(single-source)最短路徑,就一定要提到一個名詞
  • 鬆弛(relaxation)
    • 定義從起點 \(s\) 到點 \(i\) 的最短距離為 \(d_i\)
    • 有什麼條件時可以使 \(d_u\) 降低?
      • 存在一個邊 \(e = (u, v)\)
      • \(d_u > d_v + w_e\)
  • 不覺得比較適合叫他「繃緊」嗎?
s
v
u
2
25
2
  • 剛才我們定義了 \(d_i\)
  • 只要存在一個邊 \(e = (u, v)\) 滿足 \(d_v > d_u + e_w\) 就得更新 \(d_v\)
  • 怎麼更新 \(d_v\)?
  • 暴力來!
    • 先將所有點和原點距離設為 \(\infty\)(一個很大的數)
    • 只要遍歷 \(E\),將需要鬆弛的點鬆弛
      • 遍歷一次?
      • 要遍歷 \(E\) 幾次才會是對的?

遍歷 \(E\) 的次數

  • 剛剛我們已經知道遍歷 \(E\) 若干次會是好的了
    • 總有一天他會是對的
    • 跑個 while 迴圈,沒辦法鬆弛就結束?
      • 不是不行
      • 那這樣做的複雜度會是?
  • 如果觀察一下,會發現在一張無負環的簡單圖:
    • 為什麼要「無負環」?
    • 一條簡單路徑最多只有 \(|V| - 1\) 條邊
      • 只要超過 \(|V| - 1\) 條邊就會有環
    • 最多只要鬆弛 \(|V| - 1\) 次!
    • 時間複雜度 \(\mathcal O(|V||E|)\)

Bellman-Ford Code

  • 看起來超笨
  • 其實只要發現沒有鬆弛就可以跳出
struct Edge {
    int u, v, w;
};

vector<int> Bellman_Ford(int vertexCount, int source, vector<Edge>& edge) {
    vector<int> distance(vertexCount, INT32_MAX);

    distance[source] = 0;

    for (int i = 1; i < vertexCount; i++) {
        for (auto& [u, v, w]: edge) {
            distance[u] = min(distance[u], distance[v] + w);
            distance[v] = min(distance[v], distance[u] + w);
        }
    }

    return distance;
}

佇列最佳化

  • 一直比較不能再鬆弛的點好笑嗎
  • 只要跑上次有被鬆弛到的點就好了!
  • 這個做法在中國被叫做 Shortest Path Faster Algorithm
    • 謝囉
  • ​​​所以要怎麼實作「只嘗試鬆弛上次鬆弛過的點」
    • 把起點放到 queue 中
    1. 從 queue 中取出 \(u\)
    2. 用有 \(u\) 的邊去鬆弛其他點
    3. 有被鬆弛到的全部丟入 queue
    4. 重複直到 queue 沒有元素
  • 這個做法得用鄰接串列!

SPFA 的複雜度

  • 這個演算法的複雜度感覺小很多了呢
    • 究竟進步了多少呢
    • \(\mathcal O(|V||E|) \longrightarrow \mathcal O(|V||E|)\)
  • 不是,這不是完全沒有進步嗎?
  • 實際上運行起來可不是這樣
    • 對於一個隨機生成的圖,他的期望複雜度是 \(\mathcal O(|V|+|E|)\)!
    • 快的跟鬼一樣
    • 不過注意,這裡指的是「隨機生成」時
    • 現在比賽已經有開始卡這種做法的趨勢,不一定能用了

SPFA Code

  • 雖然他已經沒了,但還是看一下
typedef pair<int, int> pii;

vector<int> SPFA(vector< vector<pii>& graph, int source) {
    vector<int> distance(graph.size(), INT32_MAX);
    distance[source] = 0;

    queue<int> q;
    vector<bool> inQueue(graph.size(), 0);
    q.push(source);

    while (!q.empty()) {
        auto v = q.front();
        q.pop();
        inQueue[v] = 0;

        for (auto& [u, w]: graph[v]) {
            distance[u] = min(distance[u], distance[v] + w);

            if (inQueue[u]) continue;
            q.push(u);
            inQueue[u] = 1;
        }
    }

    return distance;
}
  • 假設圖上沒有負邊
    • 沒有負邊可以有什麼特性?
    • 如果 \(d_u\) 已經被最小化?
      • 用 \(u\) 來鬆弛別人的結果 \(d'_v \ge d_u\)
    • 取當前距離最小的點 \(u\) 來鬆弛所有人!
  • 演算法長怎樣?
    1. 從圖上距離最小的點 \(u\) 開始,標記已使用
    2. 將所有該點連到的點鬆弛
    3. 以未被使用過最小的點繼續,直到沒有點

複雜度分析

  • 可以把 Dijkstra 的過程分成兩個部分
    • 找到目前距離最小且未被用過的點
    • 修改該點連到的點之距離
  • 複雜度為 \(\mathcal O(|E| \cdot T_{modify} + |V| \cdot T_{get \min})\)
  • 幾種常見實作
    • 自平衡二元樹 \(\mathcal O((|E| + |V|) \log |E|)\)
    • 暴力做線性搜 \(\mathcal O(|E| + |V|^2)\)
    • Fibonacci heap \(\mathcal O(|E| + |V| \log |V|)\)

Dijkstra Code

typedef pair<int, int> pii;

vector<int> Dijkstra(vector< vector<pii> >& graph, int source) {
    vector<int> distance(graph.size(), INT32_MAX);
    distance[source] = 0;

    vector<bool> visited(graph.size(), 0);
    priority_queue<pii, vector<pii>, greater<pii> > pq;
    pq.push({0, source});

    while (!pq.empty()) {
        auto [w, u] = pq.top();
        pq.pop();

        if (visited[u]) continue;
        visited[u] = 1;

        for (auto& [w, v]: graph[u]) {
            if (distance[v] <= distance[u] + w) continue;

            distance[v] = distance[u] + w;
            pq.push({distance[v], v});
        }
    }

    return distance;
}

例題

建北電資 27th 演算法[12] 圖論[1]

By Brine

建北電資 27th 演算法[12] 圖論[1]

圖論[1]

  • 342