由于本章采用手写笔记尝试了一下效果,这里贴手写笔记的图片
图的分解
3.1 图是什么?
3.2 无向图上的DFS
其中previsit和postvisit在实现中我是没有写进去的,因为用不上。
接下来我们看一下源代码:
-
数据:
const int MAXN = 1e6 + 7; // 节点数量的最大值 int N = 6; // 节点数量 vector<int> G[MAXN]; // 无向图G bool visited[MAXN]; // 访问数组(false为未访问,true为已访问)
-
explore函数的实现:
vector<int> explore(int u) { visited[u] = true; // 标记已经访问过的点 vector<int> res = {u}; for (auto v: G[u]) if (!visited[v]) { vector<int> tmp = explore(v); res.insert(res.end(), tmp.begin(), tmp.end()); } return res; // 返回探测到的点的集合 }
-
dfs外层函数:
int dfs() { memset(visited, 0, sizeof visited); // visited初始化false int cnt = 0; for(int i = 1; i <= N; i++) { if (!visited[i]) { vector<int> region = explore(i); // 探索i所在的连通区 cout << "region[" << ++cnt << "]: { "; for (auto c: region) cout << c << " "; cout << "}" << endl; } } return cnt; // 返回连通区数量 }
3.3 有向图上的DFS
遍历的方法和上文中无向图的没差别。
3.4 强连通分量
3.4.1 Kosaraju算法——本书采用的算法
下面通过C++实现了Kosaraju算法,代码如下:
const int MAXN = 1e6 + 7; // 节点数量的最大值
int N = 8; // 节点数量
vector<int> G[MAXN]; // 有向图G
vector<int> GR[MAXN]; // G的反向图
int t = 0; // 离开节点时间戳
int post[MAXN]; // 离开时间对应的节点编号。例:post[i]=j表示在i时刻离开的节点编号为j
bool visited[MAXN]; // 访问数组(false为未访问,true为已访问)
int kosaraju() {
int cnt = 0;
// 先遍历正向图G打上时间戳
memset(visited, 0, sizeof visited);
for (int i = 1; i <= N; i++) {
if (!visited[i]) {
explore_G(i);
}
}
// 然后按时间序遍历反向图GR返回强连通子图
memset(visited, 0, sizeof visited);
for (int i = N; i >= 1; i--) {
int u = post[i];
if (!visited[u]) {
vector<int> region = explore_GR(u);
cout << "region[" << ++cnt << "]: { ";
for (auto c: region) cout << c << " ";
cout << "}" << endl;
}
}
// 返回强连通子图个数
return cnt;
}
void explore_G(int u) {
visited[u] = true; // 标记已经访问过的点
for (auto v: G[u])
if (!visited[v])
explore_G(v);
post[++t] = u;
}
vector<int> explore_GR(int u){
visited[u] = true; // 标记已经访问过的点
vector<int> res = {u};
for (auto v: GR[u])
if (!visited[v]) {
vector<int> tmp = explore_GR(v);
res.insert(res.end(), tmp.begin(), tmp.end());
}
return res; // 返回探测到的点的集合
}
3.4.2 Tarjan算法——蓝书拓展算法
这里采用本节第一张图作为案例,代码如下:
const int MAXN = 1e6 + 7; // 节点数量的最大值
int N = 7; // 节点数量
vector<int> G[MAXN] = { { }, { 2 }, { 3 }, { 6 }, { 3, 7 }, { 2 }, { 5, 7 }, { } }; // 有向图G
int t = 0, dfn[MAXN], low[MAXN]; // t为进入的时间,dfn为遍历顺序,low为指向的最小dfn
bool in_stack[MAXN]; // 访问数组(false为未访问,true为已访问)
stack<int> st; // 用于存放节点的栈
int tarjan() {
int cnt = 0;
memset(dfn, 0, sizeof dfn);
memset(low, 0, sizeof low);
memset(in_stack, 0, sizeof in_stack);
for (int i = 1; i <= N; i++) {
if (!dfn[i]) {
cnt += dfs(i);
}
}
return cnt;
}
int dfs(int u) {
int cnt = 0;
dfn[u] = low[u] = ++t; // dfn与low初始化为进入时间
st.push(u); in_stack[u] = true; // 已访问当前节点
// 判定递归情况
for (auto v: G[u]) {
if (!dfn[v]) {
cnt += dfs(v);
low[u] = min(low[u], low[v]);
} else if (in_stack[v]) {
low[u] = min(low[u], low[v]);
}
}
// 针对强连通分量入口节点进行栈操作
if (dfn[u] == low[u]) {
cnt++; // 确定了一个连通分量
// 弹出该连通分量所有节点
cout << "region: { ";
int top;
do {
top = st.top(); st.pop();
in_stack[top] = false;
cout << top << " ";
} while (top != u);
cout << "}" << endl;
}
return cnt;
}