强连通图:在有向图G中,如果任意两个点都是联通的(这两个点可以互相到达),那么这个图就是强连通图。
极大强连通子图:不能再扩大的强连通子图(已经成为局部最大的)。
强连通分量:在非强连通图中,其极大强连通子图可理解为该图的强连通分量。
Tarjan算法
该算法的基础:基于DFS算法,同时需要两个数组dfn和low来辅助。
dfn数组:dfn[v]表示搜索到v点的步数。
low数组:low[v]表示该点所能追溯到栈内最早的出现的那个点。
操作原理:强连通分量由好多个环组成,当有环形成时,这一条路径上的low值是统一的,即这一条路径上的所有点都属于同一个强连通分量。
算法规则:
首次搜索到u点的时候,low[u]和dfn[u]的值都为到达这个点的步数(时间)。
每次遇到一个没有被标记过的点就把这个点放入到栈中。
当点u遍历到其中一个点v的时候,如果这个点v不在栈中,low[u] = min(low[u], low[v]);如果这个点v在栈中,low[u] = min(low[u], dfn[v])。
当搜索到一个点的low和dfn相等时,栈内的这些点就构成了一个强连通分量。
算法演示:(https://www.byvoid.com/zhs/blog/scc-tarjan)
从节点1开始进行DFS,这样把1 3 5 6这四个节点都放入到了栈中,到了6的时候DFN和LOW都是4,那么{6}本身就构成了第一个强连通分量,6从栈中退出,返回至节点5。(如下图)
节点5的DFN和LOW值也是相同的,{5}本身也构成了第二个强连通分量,5从栈中退出,返回节点3。(如下图)
节点3遍历到了节点4,节点4有两条边分别到了6和1,到了6的时候6已经成为了单独的强连通分量,可以用vis[6]=2标记将其跳过,然后4找到了节点1,发现节点1在栈中,因此low[4] = min(low[4], dfn[1]) = 1,然后返回到了节点3,此时节点3的low[3] = min(low[3], low[4]) = 1。
然后返回到节点1,节点1遍历到了节点2,节点2右找到了节点4,节点4在栈中,因此low[2] = min(low[2], dfn[4]) = 1,然后返回到节点1,此时发现回到的元素dfs == low值,栈中和元素构成第三个强连通分量,算法结束。
代码模板如下:
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
//链式前向星存图
struct Edge {
int v; //边的终点
int nex; //下一条边
} edge[10005];
int edgeNum; //边的条数
int id[105];
int vis[105];
int n, m;
int componentMap[105]; //map[i]表示的是第i个点属于的强连通分量值
int componentNum; //强连通分量的个数
int dfn[105];
int low[105];
int stack[105];
int stacksize;
void dfs(int u, int step) {
dfn[u] = step;
low[u] = step;
vis[u] = 1;
stack[++stacksize] = u;
for (int i = id[u]; i != -1; i = edge[i].nex) {
int v = edge[i].v;
if (vis[v] == 0)
dfs(v, step + 1);
if (vis[v] == 1)
low[u] = min(low[u], low[v]);
}
if (dfn[u] == low[u]) {
componentNum++;
int k;
do {
k = stack[stacksize--];
componentMap[k] = componentNum;
vis[k] = 2;
} while (k != u);
}
}
void tarjan() {
componentNum = 0;
stacksize = 0;
memset(vis, 0, sizeof(vis));
for (int u = 1; u <= n; u++) {
if (vis[u] == 0)
dfs(u, 1);
}
}
void add (int bgn, int nd) {
edge[edgeNum].nex = id[bgn];
edge[edgeNum].v = nd;
id[bgn] = edgeNum++;
}
void init () {
edgeNum = 1;
for (int i = 0; i <= n; i++) {
id[i] = -1;
}
memset(edge, 0, sizeof(edge));
}
int main () {
scanf("%d %d", &n, &m);
init();
for (int i = 0; i < m; i++) {
int a, b;
scanf("%d %d", &a, &b);
add(a, b);
}
tarjan();
for (int i = 1; i <= n; i++)
cout << i << " : " << componentMap[i] << endl;
return 0;
}