Tarjan算法

首先我们需要知道强连通分量,因为Tarjan就是用来求这个的。连通的意思是对于<u,v>,存在路径可以从其中一个达到另一个;强连通是指即存在从u到v的路径,又存在从v到u的路径。强连通是针对于有向图来说的,因为无向图只要做到了连通就做到了强连通。强连通分量就是指有向图的极大强连通子图,多一个点少一个点都不能保证强连通。

求一个有向图的强连通分量有三种常用算法,Kosaraju算法、Tarjan算法、Gabow算法。其中最最常用且效率很高的是Tarjan算法。

Tarjan算法利用了dfs,定义dfn[u]表示结点u的时间戳(第几个被访问的),low[u]表示从结点u向下遍历所能找到的最小的dfn。强连通分量里必然存在环,从其中一个结点向下遍历必然会回到自身,而low中存放的是找到最小的dfn,假若一个结点的dfn=low,那么他一定是强连通分量中第一个被访问到的点。如果我们使用栈保存被访问过的结点,那么从这个结点开始往上直到栈顶就是当前强连通分量中的点。

如果我们把所有的强连通分量找出来,缩成一个点,那么这个图将变成DAG,因为不再有环。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<stack>
 5 using namespace std;
 6 int n,m,head[maxn],eid;
 7 struct edge {
 8     int v,next;
 9 } E[maxm];
10 void insert(int u,int v) {
11     E[eid].v=v;
12     E[eid].next=head[u];
13     head[u]=eid++;
14 }
15 int dfn[maxn],low[maxn],ins[maxn],vid,scc,belong[maxn];
16 stack<int> s; //用栈存放已访问的结点
17 void tarjan(int u) {
18     dfn[u]=low[u]=vid++; //初始化,打时间戳
19     s.push(u); //结点入队,同时记录结点是否在队列中
20     ins[u]=1; //不在队列中可视为属于其他强连通分量
21     for(int p=head[u];p+1;p=E[p].next) {
22         int v=E[p].v;
23         if(!dfn[v]) { //对于未访问过的结点
24             tarjan(v);
25             low[u]=min(low[u],low[v]); //更新最早的时间戳
26         } else if(ins[v]) low[u]=min(low[u],dfn[v]);
27     } //若v在栈中,说明属于当前强连通分量,也要考虑他的时间戳
28     //最后low里就是从该结点出发能找到的最小的时间戳
29     if(dfn[u]==low[u]) { //说明该结点是当前强连通分量最早被标记的点
30         ++scc;
31         while(!s.empty()) {
32             int t=s.top();s.pop();
33             ins[t]=0; //记得更新ins
34             belong[t]=scc;
35             if(t==u) break;
36         }
37     }
38 }

只从一个结点出发并不一定能找全图上所有的SCC,所以需要进行多次Tarjan。

for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);

 

posted @ 2018-09-15 19:51  Mr^Kevin  阅读(614)  评论(0编辑  收藏  举报