Tarjan 算法

一时失言乱红尘 2023-01-16 06:21 181阅读 0赞

Tarjan 算法

一.算法简介

Tarjan 算法一种由Robert Tarjan提出的求解有向图强连通分量的算法,它能做到线性时间的复杂度。

我们定义:

如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

30df5dab099891b393b66f44d164e07e.png

例如:在上图中,{1 , 2 , 3 , 4 } , { 5 } , { 6 } 三个区域可以相互连通,称为这个图的强连通分量。

Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。

再Tarjan算法中,有如下定义。

DFN[ i ] : 在DFS中该节点被搜索的次序(时间戳)

LOW[ i ] : 为i或i的子树能够追溯到的最早的栈中节点的次序号

当DFN[ i ]==LOW[ i ]时,为i或i的子树可以构成一个强连通分量。

二.算法图示

以1为Tarjan 算法的起始点,如图

04ed35f979b31993a9d61169aa44ae44.png

顺次DFS搜到节点6

e5f13b78348a9d964af24460b6ff20f2.png

回溯时发现LOW[ 5 ]==DFN[ 5 ] , LOW[ 6 ]==DFN[ 6 ] ,则{ 5 } , { 6 } 为两个强连通分量。回溯至3节点,拓展节点4.

93a73528980edd7835174f706f1af31f.png

拓展节点1 , 发现1再栈中更新LOW[ 4 ],LOW[ 3 ] 的值为1

60ab2639464175c0f30af3ca70dc2999.png

回溯节点1,拓展节点2

5336c3219fb00afc4e80a8784f207d67.png

自此,Tarjan Algorithm 结束,{1 , 2 , 3 , 4 } , { 5 } , { 6 } 为图中的三个强连通分量。

25c6e88eaa05c475db66d1c4eaaf2dbd.png

不难发现,Tarjan Algorithm 的时间复杂度为O(E+V).

三.算法模板

复制代码

  1. 1 void Tarjan ( int x ) {
  2. 2 dfn[ x ] = ++dfs_num ;
  3. 3 low[ x ] = dfs_num ;
  4. 4 vis [ x ] = true ;//是否在栈中
  5. 5 stack [ ++top ] = x ;
  6. 6 for ( int i=head[ x ] ; i!=0 ; i=e[i].next ){
  7. 7 int temp = e[ i ].to ;
  8. 8 if ( !dfn[ temp ] ){
  9. 9 Tarjan ( temp ) ;
  10. 10 low[ x ] = gmin ( low[ x ] , low[ temp ] ) ;
  11. 11 }
  12. 12 else if ( vis[ temp ])low[ x ] = gmin ( low[ x ] , dfn[ temp ] ) ;
  13. 13 }
  14. 14 if ( dfn[ x ]==low[ x ] ) {//构成强连通分量
  15. 15 vis[ x ] = false ;
  16. 16 color[ x ] = ++col_num ;//染色
  17. 17 while ( stack[ top ] != x ) {//清空
  18. 18 color [stack[ top ]] = col_num ;
  19. 19 vis [ stack[ top-- ] ] = false ;
  20. 20 }
  21. 21 top -- ;
  22. 22 }
  23. 23 }

复制代码

注:本文章上部分内容转载自http://www.cppblog.com/sosi/archive/2010/09/26/127797.html;一方面是网上有很多关于tarjan算法的介绍,我觉得都没有这个他的文章介绍的简明易懂或者没有具体的实现。另一方面,自己也顺便用java实现了一下,所以发表出来和大家分享分享!

[有向图强连通分量]

在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。

下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。

Center

[Tarjan算法]

Tarjan算法是基于对图深度优

定义DFN(u)D记录搜索到该u的时间,也就是第几个搜索u的。Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。

算法伪代码如下

tarjan(u)
{

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

}

接下来是对算法流程的演示。

从节点1开始DFS,把遍历到的节点加入栈中。搜索到节点u=6时,DFN[6]=LOW[6],找到了一个强连通分量。退栈到u=v为止,{6}为一个强连通分量。

Center 1

返回节点5,发现DFN[5]=LOW[5],退栈后{5}为一个强连通分量。

Center 2

返回节点3,继续搜索到节点4,把4加入堆栈。发现节点4向节点1有后向边,节点1还在栈中,所以LOW[4]=1。节点6已经出栈,(4,6)是横叉边,返回3,(3,4)为树枝边,所以LOW[3]=LOW[4]=1。

Center 3

继续回到节点1,最后访问节点2。访问边(2,4),4还在栈中,所以LOW[2]=DFN[4]=5。返回1后,发现DFN[1]=LOW[1],把栈中节点全部取出,组成一个连通分量{1,3,4,2}。

Center 4

至此,算法结束。经过该算法,求出了图中全部的三个强连通分量{1,3,4,2},{5},{6}。

可以发现,运行Tarjan算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为O(N+M)。

算法java实现如下:

Tarjan类:

  1. import java.util.ArrayList;
  2. import java.util.Arrays;
  3. import java.util.List;
  4. import java.util.Stack;
  5. public class Tarjan {
  6. private int numOfNode;
  7. private List< ArrayList<Integer> > graph;//图
  8. private List< ArrayList<Integer> > result;//保存极大强连通图
  9. private boolean[] inStack;//节点是否在栈内,因为在stack中寻找一个节点不方便。这种方式查找快
  10. private Stack<Integer> stack;
  11. private int[] dfn;
  12. private int[] low;
  13. private int time;//
  14. public Tarjan(List< ArrayList<Integer> > graph,int numOfNode){
  15. this.graph = graph;
  16. this.numOfNode = numOfNode;
  17. this.inStack = new boolean[numOfNode];
  18. this.stack = new Stack<Integer>();
  19. dfn = new int[numOfNode];
  20. low = new int[numOfNode];
  21. Arrays.fill(dfn, -1);//将dfn所有元素都置为-1,其中dfn[i]=-1代表i还有没被访问过。
  22. Arrays.fill(low, -1);
  23. result = new ArrayList<ArrayList<Integer>>();
  24. }
  25. public List< ArrayList<Integer> > run(){
  26. for(int i=0;i<numOfNode;i++){
  27. if(dfn[i]==-1){
  28. tarjan(i);
  29. }
  30. }
  31. return result;
  32. }
  33. public void tarjan(int current){
  34. dfn[current]=low[current]=time++;
  35. inStack[current]=true;
  36. stack.push(current);
  37. for(int i=0;i<graph.get(current).size();i++){
  38. int next = graph.get(current).get(i);
  39. if(dfn[next]==-1){//-1代表没有被访问
  40. tarjan(next);
  41. low[current]=Math.min(low[current], low[next]);
  42. }else if(inStack[next]){
  43. low[current]=Math.min(low[current], dfn[next]);
  44. }
  45. }
  46. if(low[current]==dfn[current]){
  47. ArrayList<Integer> temp =new ArrayList<Integer>();
  48. int j = -1;
  49. while(current!=j){
  50. j = stack.pop();
  51. inStack[j]=false;
  52. temp.add(j);
  53. }
  54. result.add(temp);
  55. }
  56. }
  57. }

测试类:

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. import java.util.Stack;
  4. public class Main {
  5. public static void main(String[] args) {
  6. //创建图
  7. int numOfNode = 6;
  8. List< ArrayList<Integer> > graph = new ArrayList<ArrayList<Integer>>();
  9. for(int i=0;i<numOfNode;i++){
  10. graph.add(new ArrayList<Integer>());
  11. }
  12. graph.get(0).add(1);
  13. graph.get(0).add(2);
  14. graph.get(1).add(3);
  15. graph.get(2).add(3);
  16. graph.get(2).add(4);
  17. graph.get(3).add(0);
  18. graph.get(3).add(5);
  19. graph.get(4).add(5);
  20. //调用Tarjan算法求极大连通子图
  21. Tarjan t = new Tarjan(graph, numOfNode);
  22. List< ArrayList<Integer> > result = t.run();
  23. //打印结果
  24. for(int i=0;i<result.size();i++){
  25. for(int j=0;j<result.get(i).size();j++){
  26. System.out.print(result.get(i).get(j)+" ");
  27. }
  28. System.out.println();
  29. }
  30. }
  31. }

先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。

发表评论

表情:
评论列表 (有 0 条评论,181人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Tarjan 算法

    Tarjan 算法 一.算法简介 Tarjan 算法一种由Robert Tarjan提出的求解有向图强连通分量的算法,它能做到线性时间的复杂度。   我们定义: 如果

    相关 Tarjan算法

     下面详细介绍一下Tarjan算法的基本思路:       1.任选一个点为根节点,从根节点开始。       2.遍历该点u所有子节点v,并标记这些子节点v已被访问

    相关 tarjan算法 转载

    转载博主:[点击打开链接][Link 1] 讲的非常好,肯定可以看懂。 tarjan算法,一个关于 图的联通性的神奇算法。基于DFS(迪法师)算法,深度优先搜索一张有向图。

    相关 tarjan算法讲解

    时隔好久回来复习tarjan算法,又看了许多网上的文章,在此再给一篇觉得不错的文章:[mengxiang000][] 全网最详细tarjan算法讲解,我不敢说别的。反正其他t