强连通分量是在有向图中才存在的,强连通量是指子图中的最大连通图
Tarjan算法是在DFS树中产生的
图片均来自:[算法]轻松掌握tarjan强连通分量
步骤:
例如简单有向图:
1,首先进行DFS,用数组dfn存储节点的遍历顺序:dfn[a] = 1,dfn[b] = 2 ……
2,接着从遍历最低层 —— d 来迭代可到的的最小层次(low[n]),例如 d 可以回到 b ,
而dfn[b] = 2,小于dfn[d],那么就将 low[d] 设置为2,
3,如果发现无法迭代,则输出,例如回溯到 b 时,b 无法变为更小的数字,那就输出d c b,
d,c,b即为一个连通分量
原理:
先根据DFS做出一个遍历到的次序,并用数字记录:A1,B2,C3,……
然后判断子节点不经过父节点能到达最早的位置,这样节点最早到达的数字就会因为割点导致分开
伪代码:
tarjan(u)
{
dfn[u]=low[u]=++Index
stack.push(u)
for each (u, v) in E
if (v is not visted)
tarjan(v)
low[u] = min(low[u], low[v])
else if (v in S)//直接else也可以
low[u] = min(low[u], dfn[v])
if (dfn[u] == low[u])
repeat
v = stack.pop
print v
until (u== v)
}
1,++初始化
2,进栈
3,循环子节点
4,没访问就访问min(l,l),访问了就直接min(l,d)
5,如果d == l就出栈
代码:
#include <bits/stdc++.h>
using namespace std;
map<string, int> dfn;
map<string, int> low;
map<string, set<string>> graph;
stack<string> stk;
int cnt = 0;
void Tarjan(string u)
{
dfn[u] = low[u] = ++cnt;
stk.push(u);
for(string elem : graph[u])
{
if(dfn.find(elem) == dfn.end())// 如果v未被访问
{
Tarjan(elem);
low[u] = min(low[u], low[elem]);
}
else//如果v在栈中,说明v是u的祖先
{
low[u] = min(low[u], dfn[elem]);
}
}
if(dfn[u] == low[u])
{
string scc_node;
do
{
scc_node = stk.top();
stk.pop();
cout << scc_node << " ";
}while(scc_node != u);
cout << endl;
}
}
int main() {
int n;
cin >> n; // 假设先输入边的数量
for(int i = 0;i < n; ++i)
{
string x, y;
cin >> x >> y;
graph[x].insert(y);
}
for(auto &elem : graph)
{
if(dfn.find(elem.first) == dfn.end()) Tarjan(elem.first);
}
return 0;
}
/*
9
a b
a f
b c
c d
d b
c e
d e
f g
g a
*/
代码具体步骤:
1,赋值:
dfn[u] = low[u] = ++cnt;
stk.push(u);
2,遍历子节点并更新low值
for(string elem : graph[u])
{
if(dfn.find(elem) == dfn.end())// 如果v未被访问
{
Tarjan(elem);
low[u] = min(low[u], low[elem]);
}
else//如果v在栈中,说明v是u的祖先
{
low[u] = min(low[u], dfn[elem]);
}
}
3,如果进入的节点 dfn == low ,那么就证明该输出了
if(dfn[u] == low[u])
{
string scc_node;
do
{
scc_node = stk.top();
stk.pop();
cout << scc_node << " ";
}while(scc_node != u);
cout << endl;
}