海之音
INFOR 36th 學術長 @小海_夢想特急_夢城前
圖論[2]
INFOR 36th. @小海_夢想特急_夢城前
Connectivity.
「我是你今晚的噩夢。」
「參加這社團就別想逃過我的魔爪。」
– Robert Endre Tarjan,
沒有說過
「不少他發明的算法都以他的名字命名,以至於有時會讓人混淆幾種不同的算法。」
– 維基百科
樹邊:DFS Tree 中的邊
返祖邊:剩下不是樹邊的邊
因為左邊沒有邊能不經過紅點到達右邊,所以它是割點
因為左右邊所有點都能不經過紅點到達彼此,所以它不是割點
以紅點為根的子樹中,左邊一定要經過紅點到達其他部分
所以紅點是割點
以紅點為根的子樹中所有點都可以不經由紅點連到其他部分,所以它不是割點
你會發現,你的 DFS 樹應該長這樣
而非這樣
using Graph = vector<vector<int>>;
vector<bool> cut_vertex(const Graph &graph) {
int n = graph.size();
vector<int> dfn(n, 0), low(n, 0);
vector<bool> result(n, false);
int time = 0;
auto dfs = [&](int cur, int pre, auto &&dfs) -> void {
dfn[cur] = low[cur] = ++time;
for (const int &nxt : graph[cur]) {
if (nxt == pre) continue;
if (dfn[nxt]) {
low[cur] = min(low[cur], dfn[nxt]);
}
else {
dfs(nxt, cur, dfs);
if (low[nxt] >= dfn[cur]) result[cur] = true;
low[cur] = min(low[cur], dfn[nxt]);
}
}
};
int child_count = 0;
dfn[0] = ++time;
for (const auto &child : graph[0]) {
if (dfn[child]) continue;
child_count++;
dfs(child, 0, dfs);
}
if (child_count > 1) result[0] = true;
return result;
}
Components.
「我是你今晚的噩夢。」
「參加資讀就別想逃過我的魔爪。」
– Robert Endre Tarjan,
沒有說過
「不少他發明的算法都以他的名字命名,以至於有時會讓人混淆幾種不同的算法。」
– 維基百科
「我是你今晚的噩夢。」
「參加這社團就別想逃過我的魔爪。」
– Robert Endre Tarjan,
沒有說過
「不少他發明的算法都以他的名字命名,以至於有時會讓人混淆幾種不同的算法。」
– 維基百科
「等等,那個 2 剛剛是不是出現了三次」
←當你到這裡時,會發現 low[nxt] = dfn[cur]
cur
nxt
cur
nxt
這是剛剛經過的子樹
cur
nxt
將它連起來
cur
nxt
接著是來到這裡
cur
nxt
這是我們 BCC 還沒用過的點 / stack 還剩下的範圍
cur
nxt
建方點,連起來
cur
nxt
還有一個
cur
nxt
還有一個
完成圓方樹
vector<vector<int>> block_cut_tree(const vector<vector<int>> &graph) {
int dfs_clock = 0;
stack<int> path;
vector<vector<int>> result;
result.reserve(graph.size() * 2);
vector<int> dfn(graph.size(), 0);
vector<int> low(graph.size(), INF);
auto dfs = [&](int cur, auto &&dfs) -> void {
low[cur] = dfn[cur] = ++dfs_clock;
path.push(cur);
for (const auto &nxt : graph[cur]) {
if (!dfn[nxt]) {
dfs(nxt, dfs);
low[cur] = min(low[cur], dfn[nxt]);
if (low[nxt] == dfn[cur]) { // find cut vertex / BCC
result.push_back(vector<int>(0));
int square = result.size() - 1;
for(int v = -1; v != nxt; path.pop()) {
v = path.top();
result[v].push_back(square);
result[square].push_back(v);
}
result[square].push_back(cur);
result[cur].push_back(square);
}
}
else {
low[cur] = min(low[cur], dfn[nxt]);
}
}
};
dfs(0, dfs);
return result;
}
其實邊雙連通也可以用 stack 喔
「我是你今晚的噩夢。」
「參加這社團就別想逃過我的魔爪。」
– Robert Endre Tarjan,
沒有說過
「這傢伙讓我做簡報做到腦中風。」
– 海之音
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
跑看看
返祖邊
樹邊
前向邊
交錯邊
樹 0 節點
樹 1 節點
樹 2 節點
樹邊
樹 0 節點
返祖
前向
交錯
樹 1 節點
樹 2 節點
樹邊
樹 0 節點
返祖
前向
交錯
樹 1 節點
樹 2 節點
樹邊
樹 0 節點
返祖
前向
交錯
樹 1 節點
樹 2 節點
樹邊
樹 0 節點
返祖
前向
交錯
樹 1 節點
樹 2 節點
樹邊
樹 0 節點
返祖
前向
交錯
樹 1 節點
樹 2 節點
樹邊
樹 0 節點
返祖
前向
交錯
樹 1 節點
樹 2 節點
樹邊
樹 0 節點
返祖
前向
交錯
樹 1 節點
樹 2 節點
樹邊
樹 0 節點
返祖
前向
交錯
樹 1 節點
樹 2 節點
樹邊
樹 0 節點
返祖
前向
交錯
樹 1 節點
樹 2 節點
樹邊
樹 0 節點
返祖
前向
交錯
樹 1 節點
樹 2 節點
樹邊
樹 0 節點
返祖
前向
交錯
樹 1 節點
樹 2 節點
樹邊
樹 0 節點
返祖
前向
交錯
樹 1 節點
樹 2 節點
樹邊
子樹 0
返祖
前向
交錯
子樹 1
子樹 2
超級源點
Case 1: 指到的已經被分派 SCC
已經找到對應「割點」
樹邊
返祖
前向
交錯
分支 0
分支 1
子樹根
Case 2: 指到的還沒被分派 SCC
但之後會縮點
Case 3: 指到的還沒被分派 SCC
且之後不會縮點
vector<int> tarjan_SCC(const vector<vector<int>> &graph) {
vector<int> scc(graph.size(), 0);
vector<int> dfn(graph.size(), 0);
vector<int> low(graph.size(), INF);
stack<int> path;
int dfs_clock = 0;
int scc_id = 0;
auto dfs = [&](int cur, auto &&dfs) -> void {
low[cur] = dfn[cur] = ++dfs_clock;
path.push(cur);
for (const auto &nxt : graph[cur]) {
if (scc[nxt]) continue;
if (!dfn[nxt]) dfs(nxt, dfs), low[cur] = min(low[cur], low[nxt]);
else low[cur] = min(low[cur], dfn[nxt]);
}
if (dfn[cur] == low[cur]) {
++scc_id;
for (int v = -1; v != cur; path.pop()) {
v = path.top();
scc[v] = scc_id;
}
}
};
for (int v = 0; v < graph.size(); v++)
if (!dfn[v]) dfs(v, dfs);
return scc;
}
原先 A > B > C
A 可以單向走到 B,B 可以單向走到 C
A
B
C
原先 A > B > C
反向後 A 走不到 B,B 走不到 C
這時如果在 A 裡面不管怎麼走都不會走到 B
B 可能會走到 A,但走不到 C
在走 B 前確認完 A 有哪些人,不要碰
A
B
C
vector<int> KosarajuSCC(const vector<vector<int>> &graph) {
vector<vector<int>> hparg(graph.size());
for (int u = 0; u < graph.size(); u++)
for (const auto &v : graph[u])
hparg[v].push_back(u);
vector<int> sort_result;
sort_result.reserve(graph.size());
vector<bool> visited(graph.size(), false);
auto dfs = [&](int cur, auto &&dfs) -> void {
visited[cur] = true;
for (const auto &nxt : graph[cur])
if (!visited[nxt]) dfs(nxt, dfs);
sort_result.push_back(cur);
};
for (int v = 0; v < graph.size(); v++)
if (!visited[v]) dfs(v, dfs);
vector<int> scc(graph.size(), 0);
int scc_id = 0;
auto sfd = [&](int cur, auto &&sfd) -> void {
scc[cur] = scc_id;
for (const auto &nxt : hparg[cur])
if (!scc[nxt]) sfd(nxt, sfd);
};
for (auto v_pt = scc.rbegin(); v_pt != scc.rend(); v_pt++)
if (!scc[*v_pt]) ++scc_id, sfd(*v_pt, sfd);
}
會這麼長是因為建反圖等等的都在裡面
實際上算法本體不到 20 行
重點是他不是 Tarjan ,他很直觀
有人要猜猜看總簡報頁數嗎
By 海之音
[ 建北電資聯合小社 / 四校聯合放課 ] - 圖論[2] / Graph Theory[2]