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|)\) 了
- 讓最大樹高變成 \(h\) 至少需要 \(2^h-1\) 次合併
- 如果我們開個紀錄「自己是根時樹的高度」的陣列呢
複雜度分析
- 是不是有人看不懂剛剛在幹嘛
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|))\)
- 什麼是 \(\alpha(x)\)?
- \(\Alpha(x, x)\) 的反函數
- \(\text{Ackermann function}\)
- \( \Alpha(4, 4) \approx 2^{2^{10^{19729}}}\)
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 先發現的
- 步驟:
- 選擇任意一點開始
- 選擇目前點集連出去所有邊中最小者
- 將該邊的另外一端加入當前點集
- 重複直到所有點都被加入當前點集
為什麼是對的
- 🈹性質!
為什麼是對的
- 🈹性質!
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 中
- 從 queue 中取出 \(u\)
- 用有 \(u\) 的邊去鬆弛其他點
- 有被鬆弛到的全部丟入 queue
- 重複直到 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\) 來鬆弛所有人!
- 演算法長怎樣?
- 從圖上距離最小的點 \(u\) 開始,標記已使用
- 將所有該點連到的點鬆弛
- 以未被使用過最小的點繼續,直到沒有點
複雜度分析
- 可以把 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;
}