概念:
- 强连通:有向图中两个结点如果能两两互相到达,就称这两个点强连通。
- 强连通图:有向图中所有结点都能两两到达,就称强连通图。
- 强连通分量:非强连通的有向图的极大强连通子图,称为强连通分量(SCC即Strongly Connected Componenet)。一个图中可以有多个。
- 强连通分量是一个环或者一个点。
求强连通分量一般用 Korasaju算法 和 Tarjan算法。
这里我就只记录tarjan算法。
这里有一篇大神的文章,我觉得讲的非常好。
https://www.cnblogs.com/five20/p/7594239.html
看他里面的tarjan算法的图步骤。
Tarjan算法:
简述:
深搜,如果碰到叶子结点(没有出度的结点),叶子结点就算一个连通分量,如果碰到之前走过的结点v(构成回路了),就回溯,回溯到这个结点v,这段回溯的结点又构成一个强连通分量。
理解:
碰到之前搜的结点还不能立刻回溯,因为不能保证这个是极大强连通子图,所以这是要用两个变量dfn[u], low[u], dfn[u] 记录遍历到u结点时的时间(时间戳),low[u]记录碰到之前走过的结点的最小值。所以碰到之前走过的结点的时候就和之前的low比较取最小值,这样就能保证回路回到的是最前面走的结点,这样强连通子图就是最大的。回溯的时候也要更新low[u],保证
最普遍的写法:
用栈存深搜中的点,遇到叶结点就出栈,遇到回路也而且回溯到根节点就把根节点以及之前的元素出栈。这里就用到 dfn[u] == low[u] 来判断是否要出栈了。相等表示回溯已经到达根节点,就要弹出元素。弹出的时候记录一下就好了。
变量:
数组 dfn[] 和 low[]:表示 遍历到某个结点的时间点 和 深搜树中结点的根节点(可以用来保证是最前面的根)
stack:栈,存储深搜过程中的结点。
in_stack[]:标记数组,标记结点是否在栈中。
idx:时间戳
一个存强连通分量的数据结构:看具体使用。
bcnt:强连通分量的数量。
vis:标记数组,标记结点是否已访问,这个可有可无,因为代码中可以中dfn来替代,dfn中0代表未访问,大于零表示访问过。
步骤:
深搜,
时间戳加一,
设置dfn[u]和low[u]的初始值为时间戳的值,dfn[u] = low[u] = idx,
u点入栈,标记u点入站
遍历u点的子结点:
如果没访问过就继续递归深搜,深搜完回溯时,要更新low[u]
访问过而且在栈中,表示遇到回路了,就更新low[u]
访问完子结点就判断是否要出栈。 dfn[u] == low[u] 就出栈。
强连通分量的数量加一,用一个数据结构存储当前的分量。这里我用了python的字典{k: id , v: 列表}。
伪代码:伪代码出自这本书中《Competitive Programming 3》
vi dfs_num, dfs_low, S, visited; // global variables
void tarjanSCC(int u) {
dfs_low[u] = dfs_num[u] = dfsNumberCounter++; // dfs_low[u] <= dfs_num[u]
S.push_back(u); // stores u in a vector based on order of visitation
visited[u] = 1;
for (int j = 0; j < (int)AdjList[u].size(); j++) {
ii v = AdjList[u][j];
if (dfs_num[v.first] == UNVISITED)
tarjanSCC(v.first