首先明确两个概念:时间戳和追溯点
    时间戳:dfn[u]表示节点u深度优先遍历的序号
    追溯点:low[n]表示节点u或u的子孙能通过非父子边追溯到的dfn最小的节点序号,即回到最早的过去。
    Targan算法是用来处理强连通问题(a->b且b->a)的。
    原理1:求桥

    1. #include<iostream>
    2. #include<cstring>
    3. using namespace std;
    4. const int maxn=1000+5;
    5. int n,m,head[maxn],cnt;
    6. int low[maxn],dfn[maxn],num;
    7. struct Edge{
    8. int to,next;
    9. }e[maxn<<1];
    10. void add(int u,int v){
    11. e[++cnt].next=head[u];
    12. e[cnt].to=v;
    13. head[u]=cnt;
    14. }
    15. void tarjan(int u,int fa){//求桥
    16. dfn[u]=low[u]=++num;
    17. for(int i=head[u];i;i=e[i].next){
    18. int v=e[i].to;
    19. if(v==fa)
    20. continue;
    21. if(!dfn[v]){
    22. tarjan(v,u);
    23. low[u]=min(low[u],low[v]);
    24. if(low[v]>dfn[u])
    25. cout<<u<<"—"<<v<<"是桥"<<endl;
    26. }
    27. else
    28. low[u]=min(low[u],dfn[v]);
    29. }
    30. }
    31. void init(){
    32. memset(head,0,sizeof(head));
    33. memset(low,0,sizeof(low));
    34. memset(dfn,0,sizeof(dfn));
    35. cnt=num=0;
    36. }
    37. int main(){
    38. while(cin>>n>>m){
    39. init();
    40. int u,v;
    41. while(m--){
    42. cin>>u>>v;
    43. add(u,v);
    44. add(v,u);
    45. }
    46. for(int i=1;i<=n;i++)
    47. if(!dfn[i])
    48. tarjan(1,0);
    49. }
    50. return 0;
    51. }

    原理二:求割点

    1. #include<iostream>
    2. #include<cstring>
    3. using namespace std;
    4. const int maxn=1000+5;
    5. int n,m,head[maxn],cnt,root;
    6. int low[maxn],dfn[maxn],num;
    7. struct Edge{
    8. int to,next;
    9. }e[maxn<<1];
    10. void add(int u,int v){
    11. e[++cnt].next=head[u];
    12. e[cnt].to=v;
    13. head[u]=cnt;
    14. }
    15. void tarjan(int u,int fa){//求割点
    16. dfn[u]=low[u]=++num;
    17. int count=0;
    18. for(int i=head[u];i;i=e[i].next){
    19. int v=e[i].to;
    20. if(v==fa)
    21. continue;
    22. if(!dfn[v]){
    23. tarjan(v,u);
    24. low[u]=min(low[u],low[v]);
    25. if(low[v]>=dfn[u]){
    26. count++;
    27. if(u!=root||count>1)
    28. cout<<u<<"是割点"<<endl;
    29. }
    30. }
    31. else
    32. low[u]=min(low[u],dfn[v]);
    33. }
    34. }
    35. void init(){
    36. memset(head,0,sizeof(head));
    37. memset(low,0,sizeof(low));
    38. memset(dfn,0,sizeof(dfn));
    39. cnt=num=0;
    40. }
    41. int main(){
    42. while(cin>>n>>m){
    43. init();
    44. int u,v;
    45. while(m--){
    46. cin>>u>>v;
    47. add(u,v);
    48. add(v,u);
    49. }
    50. for(int i=1;i<=n;i++)
    51. if(!dfn[i]){
    52. root=i;
    53. tarjan(i,0);
    54. }
    55. }
    56. return 0;
    57. }

    原理三:求强连通分量

    #include<iostream>
    #include<cstring>
    #include<stack>
    using namespace std;
    const int maxn=1000+5;
    int n,m,head[maxn],cnt;
    int low[maxn],dfn[maxn],num;
    stack<int>s;
    bool ins[maxn];
    struct Edge{
        int to,next;
    }e[maxn<<1];
    
    void add(int u,int v){
        e[++cnt].next=head[u];
        e[cnt].to=v;
        head[u]=cnt;    
    }
    
    void tarjan(int u){//求强连通分量 
        low[u]=dfn[u]=++num;
        cout<<"low["<<u<<"]="<<low[u]<<"\tdfn["<<u<<"]="<<dfn[u]<<endl;
        ins[u]=true;
        s.push(u);
        for(int i=head[u];i;i=e[i].next){
            int v=e[i].to;
            if(!dfn[v]){
                tarjan(v);
                low[u]=min(low[u],low[v]);
                cout<<"update1:low["<<u<<"]="<<low[u]<<endl;
            }
            else if(ins[v]){
                low[u]=min(low[u],dfn[v]);
                cout<<"update2:low["<<u<<"]="<<low[u]<<endl;
            }    
        }
        if(low[u]==dfn[u]){
            int v;
            cout<<"强连通分量:";
            do{
                v=s.top();
                s.pop();
                cout<<v<<" ";
                ins[v]=false;
            }while(v!=u);
            cout<<endl;
        }
    }
    
    void init(){
        memset(head,0,sizeof(head));
        memset(low,0,sizeof(low));
        memset(dfn,0,sizeof(dfn));
        memset(ins,0,sizeof(ins));
        cnt=num=0;
    }
    
    int main(){
        while(cin>>n>>m){
            init();
            int u,v;
            while(m--){
                cin>>u>>v;
                add(u,v);
            }
            for(int i=1;i<=n;i++)
                if(!dfn[i])
                    tarjan(i);
        }
        return 0;
    }
    /*测试数据 
    5 8
    1 3
    1 2
    3 5
    3 4
    3 2
    4 5
    4 1
    5 1
    */
    

    实际应用:

     #include<cstdio>
    
     #include<algorithm>
    
     #include<string.h>
    
     using namespace std;
    
     struct node
    
     {
    
         int v,next;
    
     } edge[1001];
    
     int DFN[1001],LOW[1001];
    
     int stack[1001],heads[1001],visit[1001],cnt,tot,index;
    
     void add(int x,int y)//链式前向心
    
     {
    
         edge[++cnt].next=heads[x];
    
         edge[cnt].v = y;
    
         heads[x]=cnt;
    
         return ;
    
     }
    
     void tarjan(int x)//代表第几个点在处理。递归的是点。
    
     {
    
         DFN[x]=LOW[x]=++tot;
    
         stack[++index]=x;
    
         visit[x]=1;//表示在栈里
    
         for(int i=heads[x]; ~i ;i=edge[i].next)
    
         {
    
             if(!DFN[edge[i].v])  //如果没访问过
    
             {
    
                 tarjan(edge[i].v);//往下进行延伸,开始递归
    
                 LOW[x]=min(LOW[x],LOW[edge[i].v]);//递归出来,比较谁是谁的儿子/父亲,就是树的对应关系,涉及到强连通分量子树最小根的事情。
    
             }
    
             else if(visit[edge[i].v ])   //如果访问过,并且还在栈里。
    
             {
    
                 LOW[x]=min(LOW[x],DFN[edge[i].v]);//比较谁是谁的儿子/父亲。就是链接对应关系
    
             }
    
         }
    
         if(LOW[x]==DFN[x]) //发现是整个强连通分量子树里的最小根。
    
         {
    
             do
    
             {
    
                 printf("%d ",stack[index]);
    
                 visit[stack[index]]=0;
    
                 index--;
    
             }
    
             while(x!=stack[index+1]); //出栈,直到找到最小根。
    
             printf("
    ");
    
         }
    
         return ;
    
     }
    
     int main()
    
     {
    
         memset(heads,-1,sizeof(heads));
    
         int n,m;
    
         scanf("%d%d",&n,&m);
    
         int x,y;
    
         for(int i=1; i<=m; i++)
    
         {
    
             scanf("%d%d",&x,&y);
    
             add(x,y);
    
         }
    
         for(int i=1; i<=n; i++)
    
             if(!DFN[i])tarjan(i);//当这个点没有访问过,就从此点开始。防止图没走完
    
         return 0;
    
     }