算法是一种用于寻找图中强连通分量的算法,它可以在线性时间内完成这个任务。强连通分量是指在有向图中,任意两个顶点之间有路径相互可达的最大子图。
算法的核心思想是使用深度优先搜索(DFS)来遍历图,同时维护一个栈来存储当前搜索路径上的顶点。当一个顶点无法继续前进时,我们从栈中弹出顶点,直到找到一个已经在栈中的顶点,这样就找到了一个强连通分量。
下面是Tarjan算法的一个C++实现:
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
// Tarjan算法寻找强连通分量的实现
class TarjanSCC {
private:
int n; // 图中顶点的数量
std::vector<std::vector<int>> adj; // 图的邻接表表示
std::vector<int> ids; // 每个顶点的唯一标识符
std::vector<int> low; // 顶点通过不经过父节点的最早的邻接点能够达到的最低标识符
std::vector<bool> onStack; // 记录顶点是否在栈中
std::stack<int> stack; // 用于存储顶点的栈
int id = 0; // 当前的标识符
int sccCount = 0; // 强连通分量的数量
// 深度优先搜索
void dfs(int at) {
ids[at] = low[at] = id++;
stack.push(at);
onStack[at] = true;
// 遍历邻接顶点
for (int to : adj[at]) {
if (ids[to] == -1) {
dfs(to);
low[at] = std::min(low[at], low[to]);
} else if (onStack[to]) {
low[at] = std::min(low[at], ids[to]);
}
}
// 如果at是一个强连通分量的根
if (ids[at] == low[at]) {
while (!stack.empty()) {
int node = stack.top();
stack.pop();
onStack[node] = false;
low[node] = ids[at];
if (node == at) {
break;
}
}
sccCount++;
}
}
public:
// 构造函数,接收图的邻接表
TarjanSCC(const std::vector<std::vector<int>>& adj) : n(adj.size()), adj(adj) {
ids.assign(n, -1);
low.assign(n, 0);
onStack.assign(n, false);
// 对每个顶点调用DFS,如果它还没有被访问过
for (int i = 0; i < n; i++) {
if (ids[i] == -1) {
dfs(i);
}
}
}
// 返回强连通分量的数量
int getSCCCount() const {
return sccCount;
}
// 返回每个顶点所属的强连通分量标识符
std::vector<int> getLowestIds() const {
return low;
}
};
int main() {
// 示例用法
int n = 8;
std::vector<std::vector<int>> adj(n);
adj = {1};
adj[1] = {2};
adj[2] = {0};
adj[3] = {1, 4};
adj[4] = {5};
adj[5] = {3, 6};
adj[6] = {7};
adj[7] = {6};
TarjanSCC scc(adj);
std::cout << "Number of Strongly Connected Components: " << scc.getSCCCount() << std::endl;
std::vector<int> lowestIds = scc.getLowestIds();
for (int i = 0; i < n; i++) {
std::cout << "Vertex " << i << " belongs to SCC " << lowestIds[i] << std::endl;
}
return 0;
}
在这个实现中,我们定义了几个重要的成员变量:
是图中顶点的数量。
是图的邻接表表示。
是每个顶点的唯一标识符,用于标识顶点在DFS遍历中的顺序。
是顶点通过不经过父节点的最早的邻接点能够达到的最低标识符。
是一个布尔数组,用于记录顶点是否在栈中。
用于存储顶点的栈。
是当前的标识符,用于给顶点分配唯一的标识符。
是强连通分量的数量。
在
函数中,我们首先给当前顶点分配一个唯一的标识符
,并将其压入栈中。然后,我们遍历邻接顶点,如果邻接顶点还没有被访问过,我们递归地调用
函数。如果邻接顶点已经在栈中(即它是当前搜索路径上的一个祖先),我们更新
为
的最小值。
当我们完成对顶点的所有邻接点的遍历时,如果
等于
,这意味着我们找到了一个强连通分量的根。我们从栈中弹出顶点,直到找到根节点,并将所有弹出的顶点标记为同一个强连通分量。
最后,在main
函数中,我们创建了一个示例图,并调用
类来找到强连通分量。然后,我们输出强连通分量的数量,并打印出每个顶点所属的强连通分量标识符。