算法流程:
G<V,E>是我们要遍历的有向图,V代表顶点集,E代表边集。S是栈,初始为空
1)while栈中顶点不包含V:
从V中选择一个不在栈中的顶点,开始DFS,每当一个顶点的拓展结束的时候,将其加入栈。
2)将每个顶点标记为0,表示还没有并入联通块,连通块计数器初始为0
3)while 栈不为空:
从栈顶取顶点,直到取得没有被标记的顶点,计数器+1,开始DFS,标记遇到的顶点。
=====================
参考:http://lcm.csa.iisc.ernet.in/dsa/node171.html
简单证明:
如果dfs2时x是root,u是x的子孙。则G中存在u -> x的路径。
=> x在栈中的顺序一定在u后面
=> 1)dfs1中u的递归开始在x之前 2)dfs1中x的递归开始在u前
=> 若1),则u可以访问到x,x的递归在u之前结束,x先入栈,矛盾。
=> 若2),则在x的递归过程中访问了u
=> G中存在 x->u ,则x,u在同一个scc中
--------------------------------------------------------------------------------------------------------
时间复杂度:O(V+E)
重要性质:求出的连通块是按照topo顺序排列的
int used[Maxn], sccNo[Maxn], V;
vector<int> g[Maxn], rg[Maxn], vs;
void dfs(int now) {
used[now] = 1;
int sz = g[now].size(), v;
rep(i, 0, sz-1) {
v = g[now][i];
if (!used[v]) dfs(v);
}
vs.push_back(now);
}
void rdfs(int now, int flag) {
used[now] = 1;
sccNo[now] = flag;
int sz = rg[now].size(), v;
rep(i, 0, sz-1) {
v = rg[now][i];
if (!used[v]) rdfs(v, flag);
}
}
int scc() {
memset(used, 0, sizeof(used));
vs.clear();
//rep(i, 0, V-1)
// if (!used[i]) dfs(i);
urep(i, V-1, 0)
if (!used[i]) dfs(i);
memset(used, 0, sizeof(used));
int cnt = 0;
urep(i, V-1, 0)
if (!used[vs[i]]) rdfs(vs[i], cnt++);
return cnt;
}
void add_edge(int from, int to) {
g[from].push_back(to);
rg[to].push_back(from);
}
void test() {
V = 9;
rep(i, 0, V-1) {
g[i].clear();
rg[i].clear();
}
add_edge(1, 2);
add_edge(2, 4);
add_edge(3, 2);
add_edge(4, 0);
add_edge(4, 5);
add_edge(4, 6);
add_edge(5, 3);
add_edge(5, 6);
add_edge(5, 7);
add_edge(6, 7);
add_edge(7, 6);
add_edge(7, 8);
add_edge(8, 0);
int cnt = scc();
cout << cnt << " scc\n";
rep(i, 0, cnt-1) {
cout << "{ ";
rep(j, 0, V-1)
if (sccNo[j] == i) cout << j << ' ';
cout << "}\n";
}
}