Tarjan

来源:互联网 发布:两期二叉树模型 知乎 编辑:程序博客网 时间:2024/05/22 12:56

Tarjan 算法

一.算法简介

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

 

我们定义:

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

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

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

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

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

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

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

 

二.算法图示

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

顺次DFS搜到节点6

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

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

 回溯节点1,拓展节点2

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

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

1.  #include<iostream>  

2. using namespace std;  

3.  int DFN[105];                                  //记录在做dfs时节点的搜索次序  

4. int low[105];                                  //记录节点能够找到的最先访问的祖先的记号  

5.  int count=1;                                   //标记访问次序,时间戳  

6. int stack[105];                                //压入栈中  

7.  int top=-1;  

8. int flag[105];                                 //标记节点是否已经在栈中  

9.  int number=0;  

10.int j;  

11. int matrix[105][105]={{0,1,1,0,0,0},{0,0,0,1,0,0},{0,0,0,1,1,0},{1,0,0,0,0,1},{0,0,0,0,0,1},{0,0,0,0,0,0}};  

12.int length;                                    //图的长度  

13. void tarjan(int u){  

14.    DFN[u]=low[u]=count++;                     //初始化两个值,自己为能找到的最先访问的祖先  

15.     stack[++top]=u;  

16.    flag[u]=1;                                 //标记为已经在栈中  

17.   

18.    for(int v=0;v<length;v++){  

19.     if(matrix[u][v]){  

20.        if(!DFN[v]){                       //如果点i没有被访问过  

21.         tarjan(v);                     //递归访问  

22.        if(low[v]<low[u])  

23.             low[u]=low[v];             //更新能找的到祖先  

24.        }  

25.         else{                              //如果访问过了,并且该点的DFN更小,则  

26.        if(DFN[v]<low[u]&&flag[v])     //flag[v]这个判断条件很重要,这样可以避免已经确定在其他联通图的v,因为uv的单向边而影响到ulow  

27.         low[u]=DFN[v];                 //也就是已经确定了的联通图要剔除掉,剔除的办法就是判断其还在栈中,因为已经确定了的连通图的点  

28.        }                                  //flag在下面的do while中已经设为0(即已经从栈中剔除了)  

29.     }  

30.    }  

31.   

32.    //往后回溯的时候,如果发现DFNlow相同的节点,就可以把这个节点之后的节点全部弹栈,构成连通图  

33.     if(DFN[u]==low[u]){  

34.    number++;                               //记录连通图的数量  

35.     do{  

36.        j=stack[top--];                     //依次取出,直到u  

37.         cout<<j<<" ";  

38.        flag[j]=0;                          //设置为不在栈中  

39.     }while(j!=u);  

40.        cout<<endl;  

41.     }  

42.}  

43. int main(){  

44.      

45.     memset(DFN,0,sizeof(DFN));                  //数据的初始化  

46.    memset(low,0,sizeof(low));  

47.     memset(flag,0,sizeof(flag));  

48.      

49.     length=6;  

50.    tarjan(0);  

51.   

52.    cout<<endl;  

53.     for(int i=0;i<6;i++){  

54.    cout<<"DFN["<<i<<"]:"<<DFN[i]<<" low["<<i<<"]:"<<low[i]<<endl;  

55.     }  

56.    return 0;  

57. }  

1 0