Kosaraju算法找强连通分量(SCC)
强连通分量SCC
在有向图中,如果某个点集中任意两点u,v都满足u可以到达v,v也可以到达u,则该点集为一个强连通分量.
注意:
- 强连通分量可以是一个点,也可以是多个点.
- 若几个点形成环,一定属于同一个强连通分量.
Kosaraju算法核心步骤
- 对原图dfs.任取一点dfs,返回时入栈,直至所有点都被访问过.
- 构建反图,将所有边反转.
- 在反图上按栈的顺序dfs,一次dfs访问到的所有点构成一个强连通分量.
原理(个人理解):
- 为什么两次dfs?
强连通块中的点必须从任意一点出发dfs能到达任意一点.第一次正向dfs把某个方向上能到达的点放在一起(栈中连续摆放),相当于将点分类,去除了这个方向上就不满足的点.第二次dfs反向进行,确保另一个方向上也能到达.
- 为什么第一次dfs时要在返回时入栈,而不是访问时入栈?
返回时入栈相当于后序遍历,保证这个点和这个点的子图都检查完毕,该点以下的点都能访问到.入栈顺序按访问时间降序排列,越后入栈的点越"重要",保证了我们在反图上从"最重要的点"(拓扑顺序最靠后的)开始第二次 DFS,从而找出整个强连通分量.
代码示例
图的基本框架
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_NODES 1000
typedef struct Node{
int vertex;
struct Node* next;
} Node;
// 添加边
void addEdge(Node* g[],int v1,int v2) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->vertex = v2;
newNode->next = g[v1];
g[v1] = newNode;
}
// 清理内存
void freeGraph(Node* graph[], int n) {
for (int i = 0; i < n; i++) {
Node* current = g[i];
while (current != NULL) {
Node* temp = current;
current = current->next;
free(temp);
}
g[i] = NULL;
}
}
int main() {
int n = 5; // 顶点个数
addEdge(g, 0, 2); // 样例
addEdge(g, 2, 1);
addEdge(g, 1, 0);
addEdge(g, 0, 3);
addEdge(g, 3, 4);
kosaraju(n);
freeGraph(g, n);
freeGraph(revg, n);
return 0;
}
Kosaraju算法部分
// 正向dfs,建立栈
void dfs1(int v) {
vis[v] = 1; // 标记为已访问
Node* tmp = g[v]; // 遍历v的出点
while (tmp) {
if (!vis[tmp->vertex]) {
dfs1(tmp->vertex); // 递归dfs
}
tmp = tmp->next;
}
stack[++top] = v; // 返回时入栈,逆序为访问顺序
}
// 反向dfs,输出强连通分量
void dfs2(int v) {
vis[v] = 1; // 标记为已访问
printf("%d ", v); // 该点在这个强连通块中
Node* tmp = revg[v]; // 遍历反图中v的出点
while (tmp) {
if (!vis[tmp->vertex]) {
dfs2(tmp->vertex); // 递归dfs
}
tmp = tmp->next;
}
}
void kosaraju(int n) {
// 第一次dfs
memset(vis, 0, sizeof(vis));
for (int i = 0; i < n; i++) {
if (!vis[i]) {
dfs1(i);
}
}
// 构建反图
for (int i = 0; i < n; i++) {
Node* tmp = g[i];
while (tmp) {
addEdge(revg, tmp->vertex, i);
tmp = tmp->next;
}
}
// 反向dfs
memset(vis, 0, sizeof(vis));
printf("strongly connected components: \n");
while (top != -1) {
int v = stack[top--];
if (!vis[v]) {
dfs2(v);
printf("\n");
}
}
}
时间复杂度
| 步骤 | 操作 | 时间复杂度 |
|---|---|---|
| 1 | 正向 DFS 构建栈 | O(V + E) |
| 2 | 图反转 | O(V + E) |
| 3 | 反向 DFS 查找SCC | O(V + E) |
| 总计 | O(V + E) |