圖論[1]
INFOR 36th. @小海_夢想特急_夢城前
Topological Sort.
ㄆ
ㄨ
Queue: {}
Result: {}
Queue: {0, 4}
Result: {}
Queue: {4, 1}
Result: {0}
Queue: {1}
Result: {0, 4}
Queue: {2}
Result: {0, 4, 1}
Queue: {3}
Result: {0, 4, 1, 2}
Queue: {}
Result: {0, 4, 1, 2, 3}
vector<vector<int>> graph;
vector<int> topological_sort() {
int n = graph.size();
vector<int> result;
vector<int> indegree(n, 0);
queue<int> nexts;
// count indegree
for (int i = 0; i < n; i++)
for (int j = 0; j < graph[i].size(); j++)
indegree[graph[i][j]]++;
for (int i = 0; i < n; i++)
if (indegree[i] == 0) nexts.push(i);
while (!nexts.empty()) {
int cur = nexts.front();
nexts.pop();
result.push(cur);
for (int i = 0; i < graph[cur].size(); i++) {
int nxt = graph[cur][i];
indegree[nxt]--;
if (indegree[nxt] == 0) nexts.push(nxt);
}
}
return result;
}ult;
}
扣
struct Node {
int in_time, out_time;
bool visited = false;
vector<int> neighbor;
};
void dfs(int cur, vector<Node> &graph) {
static int time_stamp = 0;
graph[cur].in_time = time_stamp++;
graph[cur].visited = true;
for (const auto &nxt : graph[cur].neighbor)
if (!graph[nxt].visited) dfs(nxt, graph);
graph[cur].out_time = time_stamp++;
}
vector<int> topological_sort(const vector<vector<int>> &graph) {
vector<int> result;
result.reserve(graph.size());
vector<int> in_time(graph.size(), 0), out_time(graph.size(), 0);
auto dfs = [&](int cur, auto &&dfs) -> bool {
static int time_stamp = 0;
in_time[cur] = ++time_stamp;
for (const auto &nxt : graph[cur]) {
if (!in_time[nxt]) {
if (!dfs(nxt, dfs)) return false;
}
else if (!out_time[nxt]) {
return false;
}
}
result.push_back(cur);
out_time[cur] = ++time_stamp;
return true;
};
for (int origin = 0; origin < graph.size(); origin++)
if (!in_time[origin])
if (!dfs(origin, dfs)) return vector<int>(0);
return result;
}
注意如果是用 vector 直接 push_back 排出來的結果是由輩分小到大
bool dfs(int cur, const vector<vector<int>> &graph, vector<int> &result, vector<int> &in_time, vector<int> &out_time) {
static int time_stamp = 0;
in_time[cur] = ++time_stamp;
for (const auto &nxt : graph[cur]) {
if (!in_time[nxt]) {
if (!dfs(nxt, graph, result, in_time, out_time)) return false;
} else if (!out_time[nxt])
return false;
}
result.push_back(cur);
out_time[cur] = ++time_stamp;
return true;
}
vector<int> topological_sort(const vector<vector<int>> &graph) {
vector<int> result;
result.reserve(graph.size());
vector<int> in_time(graph.size(), 0), out_time(graph.size(), 0);
for (int origin = 0; origin < graph.size(); origin++)
if (!in_time[origin])
if (!dfs(origin, graph, result, in_time, out_time)) return vector<int>(0);
return result;
}
給看不懂 lambda 函式的人
Shortest Path.
vector<int> Bellman_Ford(int origin, int n, const vector<vector<pair<int, int>>> &graph) {
vector<int> d(graph.size(), INF); // 從源點到各個點的答案
for (int i = 0; i < graph.size(); i++)
for (int v = 0; v < graph.size(); v++)
for (const pair<int, int> &edge: graph[v])
#define edge.second w
#define edge.first u
if (d[v] + w < d[u]) d[u] = d[v] + w;
return d;
}
vector<int> SPFA(int origin, const vector<vector<pair<int, int>>> &graph;) {
vector<int> d(graph.size(), INF);
vector<bool> inQueue(graph.size(), false);
queue<int> nexts;
nexts.push(origin);
inQueue[origin] = true;
while (!nexts.empty()) {
int v = nexts.front();
nexts.pop();
inQueue[v] = false;
for (const pair<int, int> &edge : graph[v]) {
#define w edge.second
#define u edge.first
if (d[v] + w < d[u] && !inQueue[u]) {
d[u] = d[v] + w;
nexts.push(u);
inQueue[u] = true;
}
}
}
return d;
}
struct Edge {
int v, w;
friend bool operator<(const Edge& a, const Edge& b) {
return a.w > b.w;
}
};
vector<int> Dijkstra(int origin) {
vector<int> d(graph.size(), INF);
vector<bool> visited(graph.size(), false);
d[origin] = 0;
priority_queue<Edge> nexts;
nexts.push({origin, 0});
while (!nexts.empty()) {
Edge cur = nexts.top();
nexts.pop();
if (visited[cur.v]) continue;
visited[cur.v] = true;
for (const Edge& e : graph[cur.v]) {
if (d[cur.v] + e.w >= d[e.v]) continue;
d[e.v] = d[cur.v] + e.w;
nexts.push({e.v, d[e.v]});
}
}
return d;
}
吃點毒
處理負環
處理負權
帶環
時間複雜度
處理負環
處理負權
帶環
時間複雜度
處理負環
處理負權
帶環
時間複雜度
帶負權/負環時用
一般情況最常用
補充
一樣,處理不了負環
但是負權會是好的
vector<vector<int>> Floyd_Warshall(const vector<vector<pair<int, int>>> &graph) {
vector<vector<int>> result(graph.size(), vector<int>(graph.size(), INF));
for (int i = 0; i < graph.size(); i++) result[i][i] = 0;
for (int u = 0; u < graph.size(); u++)
for (const auto &[v, w] : graph[u])
result[u][v] = w;
for (int k = 0; k < graph.size(); k++)
for (int u = 0; u < graph.size(); u++)
for (int v = 0; v < graph.size(); v++)
result[u][v] = min(result[u][v], result[u][k] + result[k][v]);
return result;
}
Disjoint Set Union Algorithm, DSU
「我是你今晚的噩夢。」
「參加資讀就別想逃過我的魔爪。」
– Robert Endre Tarjan,
沒有說過
「不少他發明的算法都以他的名字命名,以至於有時會讓人混淆幾種不同的算法。」
– 維基百科
Dis - 不, Joint - 共同, Set - 集合
有交集
Dis - 不, Joint - 共同, Set - 集合
不交集
簡單來說,各集合彼此之間沒有交集
啊啊爛了,1 和 2 回傳的代表元素不同
查詢
struct DSU {
vector<int> master;
DSU(int n) {
master.resize(n);
for (int i = 0; i < n; i++) master[i] = i;
}
int find(int i) {
if (master[i] == i) return i;
return find(master[i]);
}
};
struct DSU {
void combine(int a, int b) {
master[find(a)] = find(b);
}
};
合併
看起來的樣子
看起來的樣子
Merge(2, 3)
查詢完後有效降低樹高了!
查詢完後有效降低樹高了!
但這樣單次查詢還是 O(n) 耶...
樹高:根到最遠點路徑長
樹高:根到最遠點路徑長
樹高:根到最遠點路徑長
新樹高:
struct DisjointSet {
vector<int> master, depth;
DisjointSet(int n) {
master.resize(n);
depth.resize(n, 0);
for (int i = 0; i < n; i++) master[i] = i;
}
int find(int n) {
if (n == master[n]) return n;
return (master[n] = find(master[n]));
}
void combine(int a, int b) {
a = find(a), b = find(b);
if (a == b) return;
if (depth[a] < depth[b]) swap(a, b);
else if (depth[a] == depth[b]) depth[a]++;
master[b] = a;
}
};
綜合
APCS 2023 十月場 P3 / ZJ m372
Minimum Spanning Tree, MST.
namespace MST {
struct DSU {
vector<int> master, depth;
DSU(int n) {
master.resize(n);
depth.resize(n, 0);
for (int i = 0; i < n; i++) master[i] = i;
}
int find(int n) {
return (n == master[n]) ? n : (master[n] = find(master[n]));
}
void combine(int a, int b) {
a = find(a), b = find(b);
if (a == b) return;
if (depth[a] < depth[b]) swap(a, b);
else if (depth[a] == depth[b]) depth[a]++;
master[b] = a;
}
};
vector<vector<pair<int, int>>> Kruskal(const vector<vector<pair<int, int>>> &graph) {
DSU components(graph.size());
vector<pair<int, pair<int, int>>> edges; // {w, {u, v}}
vector<vector<pair<int, int>>> result(graph.size());
for (int u = 0; u < graph.size(); u++)
for (const auto &[v, w] : graph[u])
edges.push_back({w, {u, v}});
sort(edges.begin(), edges.end());
for (const auto &[w, e] : edges) {
#define u e.first
#define v e.second
if (components.find(u) != components.find(v)) {
components.combine(u, v);
result[u].push_back({v, w}), result[v].push_back({u, w});
}
}
#undef u
#undef v
return result;
}
}
事實上如果原本圖就用邊串列存會方便很多
如果用鄰接串列存圖建議用下一個寫法
typedef pair<int, int> pii;
typedef pair<int, pii> pipii;
typedef vector<vector<pair<int, int>>> Graph;
Graph Prim(const Graph &graph) {
// edge: {w, v}
Graph result(graph.size());
vector<bool> visited(graph.size(), false);
priority_queue<pipii, vector<pipii>, greater<pipii>> nexts;
#define w first
#define u second.first
#define v second.second
visited[0] = true;
for (const auto &[nxt_w, nxt_v] : graph[0])
nexts.push({nxt_w, {0, nxt_v}});
while (!nexts.empty()) {
auto cur_edge = nexts.top();
nexts.pop();
if (visited[cur_edge.v]) continue;
visited[cur_edge.v] = true;
result[cur_edge.u].push_back({cur_edge.w, cur_edge.v});
result[cur_edge.v].push_back({cur_edge.w, cur_edge.u});
for (const auto &[nxt_w, nxt_v] : graph[cur_edge.v])
nexts.push({nxt_w, {cur_edge.v, nxt_v}});
}
#undef u
#undef v
#undef w
return result;
}