Kosaraju算法求有向强连通分量,缩点后是DAG的拓扑序列(从小到大)
强连通分量分解
对于一个有向图顶点的子集S,如果在S内任取两个顶点u和v,都能找到一条从u到v的路径,那么
就称S是强连通的。如果在强连通的顶点集合S中加入其他任意顶点集合后,它都不再是强连通的,
那么就称S是原图的一个强连通分量(SCC:Strongly Connected Component) 。任意有向图都可以
分解成若干不相交的强连通分量,这就是强连通分量分解。把分解后的强连通分量缩成一个顶点,
就得到了一个DAG(有向无环图)。
【Kosaraju算法求有向强连通分量,缩点后是DAG的拓扑序列(从小到大)】
文章图片
强连通分量分解可以通过两次简单的DFS实现。第一次DFS时,选取任意顶点作为起点,遍历所
有尚未访问过的顶点,并在回溯前给顶点标号(post order,后序遍历) 。对剩余的未访问过的顶
点,不断重复上述过程。
完成标号后,越接近图的尾部(搜索树的叶子),顶点的标号越小。第二次DFS时,先将所有边
反向,然后以标号最大的顶点为起点进行DFS。这样DFS所遍历的顶点集合就构成了一个强连通
分量。之后,只要还有尚未访问的顶点,就从中选取标号最大的顶点不断重复上述过程。
文章图片
正如前文所述,我们可以将强连通分量缩点并得到DAG。此时可以发现,标号最大的节点就属于
DAG头部(搜索树的根)的强连通分量。因此,将边反向后,就不能沿边访问到这个强连通分量
以外的顶点。而对于强连通分量内的其他顶点,其可达性不受边反向的影响,因此在第二次DFS
时,我们可以遍历一个强连通分量里的所有顶点。
文章图片
该算法只进行了两次DFS,因而总的复杂度是O(|V|+|E|)。
int V;
// 顶点数
vector G[MAX_V];
// 图的邻接表表示
vector rG[MAX_V];
// 把边反向后的图
vector vs;
// 后序遍历顺序的顶点列表
bool used[MAX_V];
// 访问标记
int cmp[MAX_V];
// 所属强连通分量的拓扑序322 第 4 章 登峰造极——高级篇
void add_edge(int from, int to)
{
G[from].push_back(to);
rG[to].push_back(from);
}
void dfs(int v)
{
used[v] = true;
for (int i = 0;
i < G[v].size();
i++)
{
if (!used[G[v][i]]) dfs(G[v][i]);
}
vs.push_back(v);
}
void rdfs(int v, int k)
{
used[v] = true;
cmp[v] = k;
for (int i = 0;
i < rG[v].size();
i++)
{
if (!used[rG[v][i]]) rdfs(rG[v][i], k);
}
}
int scc()
{
memset(used, 0, sizeof(used));
vs.clear();
for (int v = 0;
v < V;
v++)
{
if (!used[v]) dfs(v);
}
memset(used, 0, sizeof(used));
int k = 0;
for (int i = vs.size() - 1;
i >= 0;
i--)
{
if (!used[vs[i]]) rdfs(vs[i], k++);
}
return k;
}
推荐阅读
- 画解算法(1.|画解算法:1. 两数之和)
- Guava|Guava RateLimiter与限流算法
- 有句话忍很久了,女生要求买房怎么就物质了()
- 一个选择排序算法
- 基于爱,才会有“愿望”当“要求”。2017.8.12
- SG平滑轨迹算法的原理和实现
- 先放下|先放下 ,求一个好心情
- 《算法》-图[有向图]
- https请求被提早撤回
- 遇到不正当请求怎么办