您的位置:首页 > 其它

Tarjan学习笔记

2018-07-12 15:36 141 查看

引言

在有向图$G$中,如果两个节点能够相互到达,那么这就是一个连通分量,如果每两个节点都连通,那么这就是一个强联通图。

有向图的极大强连通子图成为一个强连通分量,而$Tarjan$算法便是用来求强连通分量的算法。

算法介绍

$Tarjan$算法是由$Robert\ Tarjan$提出的,该算法时间复杂度为$O(N+M)$,属于线性复杂度。

再此膜拜神仙 $\%\%\%\ STO\ Tarjan\ ORZ\ \%\%\%$

$Tarjan$算法是用$DFS$实现的算法,在一棵搜索树当中,每一个强连通分量都是一个子树

定义$DFN(U)$表示节点$U$被访问到的次序编号,$LOW(U)$表示节点$U$或者$U$的子树在退栈时所能遍历到的最小的次序编号

显而易见,当$DFN(U) == LOW(U)$时,以$U$节点为$root$的子树就是一个强联通分量。

我们在遍历的时候,如果先将当前的节点压入栈中,由$vis$数组对其进行标记,表示已经入过栈然后遍历这个节点的所有边

如果边的终点的$DFN$已经有值了,就代表$DFN$已经访问过了,那么就不需要对其进行遍历,只需要确定它与当前节点的关系

否则就对其进行遍历,在回溯的时候将其$LOW$值与经过的点进行比较取最小值

下面是$Tarjan$算法的伪代码,来自BYvoid大神的blog,如果你想进去,请先挂上梯子

tarjan(u)
{
DFN[u]=Low[u]=++Index                      // 为节点u设定次序编号和Low初值
Stack.push(u)                              // 将节点u压入栈中
for each (u, v) in E                       // 枚举每一条边
if (v is not visted)               // 如果节点v未被访问过
tarjan(v)                  // 继续向下找
Low[u] = min(Low[u], Low[v])
else if (v in S)                   // 如果节点v还在栈内
Low[u] = min(Low[u], DFN[v])
if (DFN[u] == Low[u])                      // 如果节点u是强连通分量的根
repeat
v = S.pop                  // 将v退栈,为该强连通分量中一个顶点
print v
until (u== v)
}


流程演示

接下来的图片也是来自BYvoid的blog

注:左边的绿色的框框代表堆栈,最底下是栈顶哦^(* ̄(oo) ̄)^

step1

先从节点$1$开始遍历,一直遍历到节点$6$,$LOW(6) == DFN(6)$,说明节点$6$就是一个强连通分量



step2

回溯到节点$5$之后,发现$LOW(5) == DFN(5)$,同理,节点$5$也是一个强连通分量



step3

继续回溯到节点$3$,发现节点$3$还有边可以走,遍历到节点$4$,用从节点$4$遍历到$1$,这时发现$1$已经在栈里了。节点$4$的$LOW$值就变成了$1$



step4

继续回溯,回溯到节点$1$之后,又遍历到节点$2$,又从节点$2$遍历到节点$4$,这时发现$4$已经在栈里了,同理,节点$2$的$LOW$值将变成$5$,因为是将节点$4$的$DFN$值和节点$2$的$LOW$值取最小值

然后回溯到节点$1$,发现节点$1$的$LOW$值和$DFN$值相等,就开始退栈,一直到栈顶元素不等于$1$



至此,该算法完成,这张图一共有三个强连通分量,分别是

1 2 3 4
5
6


 

复杂度分析

至于$Tarjan$算法的复杂度,因为每个节点只入过一次栈,并且每条边只访问过一次,所以它的时间复杂度是线性的,为$O(M+N)$

吐槽一句,这个算法的名字的读法,很奇怪,我出去学习的时候老师说应该读tǎ yáng,但是我的学长们说应该对tǎ jiān,不过,我比较喜欢叫它tài jiān,这多接地气。。。。QwQ

Tarjan代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stack>
#include <cmath>
#define MAXN 100050
#define INF  2147483647

using namespace std;

int n, m, tot;
int u[MAXN], v[MAXN], w[MAXN];
int first[MAXN], next[MAXN];
int dfn[MAXN], low[MAXN];
bool vis[MAXN];
stack<int> S;

void Tarjan(int x) {
dfn[x] = low[x] = ++tot;
S.push(x);
vis[x] = 1;
int k = first[x];
while(k != -1) {
if(!dfn[v[k]]) {
Tarjan(v[k]);
low[x] = min(low[x], low[v[k]]);
}
else if(vis[v[k]] == 1&&dfn[v[k]]) {
low[x] = min(low[x], low[v[k]]);
}
k = next[k];
}
if(dfn[x] == low[x]) {
while(!S.empty()) {
int temp = S.top();
S.pop();
printf("%d ", temp);
vis[temp] = 0;
if(temp == x) break;
}
printf("\n");
}
return ;
}

int main() {
scanf("%d%d", &n, &m);
memset(first, -1, sizeof(first));
for(int i=1; i<=m; i++) {
scanf("%d%d", &u[i], &v[i]);
w[i] = 1;
next[i] = first[u[i]];
first[u[i]] = i;
}
for(int i=1; i<=n; i++)
if(!dfn[i])
Tarjan(i);
return 0;
}


  

附赠大家一组样例

Sample Input

6 8
1 3
1 2
2 4
3 4
3 5
4 6
4 1
5 6


  

Sample Output

6
5
3 4 2 1


  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: