zoukankan      html  css  js  c++  java
  • tarjan算法(割点/割边/点连通分量/边连通分量/强连通分量)

    tarjan算法是在dfs生成一颗dfs树的时候按照访问顺序的先后,为每个结点分配一个时间戳,然后再用low[u]表示结点能访问到的最小时间戳

    以上的各种应用都是在此拓展而来的。

    割点:如果一个图去掉某个点,使得图的连通分支数增加,那么这个点就是割点

    某个点是割点,当且仅当这个点的后代没有连回自己祖先的边。即low[v] >= dfn[u]     , v是u的后代

    需要注意的是根结点的特判,因为根结点没有祖先,根结点是割点,当且仅当根结点有两个以上的儿子。

    问题:重边对该算法有影响吗?没有影响。 

       需要注意的地方? 图至少有三个点以上, 否则需要注意一下。

      

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <stdlib.h>
     4 #include <algorithm>
     5 #include <iostream>
     6 #include <queue>
     7 #include <stack>
     8 #include <vector>
     9 #include <map>
    10 #include <set>
    11 #include <string>
    12 #include <math.h>
    13 using namespace std;
    14 typedef long long LL;                   
    15 const int INF = 1<<30;
    16 const int N = 1000 + 10;
    17 vector<int> g[N];
    18 int dfs_clock,dfn[N],low[N];
    19 bool isCut[N];
    20 void init(int n)
    21 {
    22     dfs_clock = 0;
    23     for(int i=0; i<=n; ++i)
    24         dfn[i] = low[i] = isCut[i] = 0;
    25 }
    26 //重边对割点没有影响,且该算法对有向图同样适用
    27 void tarjan(int u, int fa)
    28 {
    29     dfn[u] = low[u] = ++dfs_clock;
    30     int child = 0;
    31     for(int i=0; i<g[u].size(); ++i)
    32     {
    33         int v = g[u][i];
    34         if(v==fa) continue;//如果是树枝边的反向访问,则不能用来更新low[u]
    35         child++;
    36         if(dfn[v]==0)
    37             tarjan(v,u);
    38         low[u] = min(low[u],low[v]);//用树枝边,或者后向边来跟新low[u]
    39         if(low[v] >= dfn[u])
    40             isCut[u] = true;
    41     }
    42     if(fa==-1 && child>=2) isCut[u] = true;
    43 }
    44 int main()
    45 {
    46     int n,m,i,u,v;
    47     while(scanf("%d%d",&n,&m)!=EOF)
    48     {
    49         init(n);
    50         for(i=0; i<m; ++i)
    51         {
    52             scanf("%d%d",&u,&v);
    53             g[u].push_back(v);
    54             g[v].push_back(u);
    55         }
    56         tarjan(1,-1);
    57         for(i=1; i<=n; ++i)
    58             if(isCut[i])
    59                 printf("%d ",i);
    60         puts("");
    61     }
    62     return 0;
    63 }
    View Code

    割边:如果一个图去掉某条边,使得图的连通分支数增加,那么这条边就是割边(桥)

    某条边是割边,当且仅当某个点的后代没有连回自己或者自己祖先的边,即low[v] > dfn[u],  那么边(u,v)是割边

    问题:重边对该算法有影响吗? 有影响。 所以要判断是不是有重边

      

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <stdlib.h>
     4 #include <algorithm>
     5 #include <iostream>
     6 #include <queue>
     7 #include <stack>
     8 #include <vector>
     9 #include <map>
    10 #include <set>
    11 #include <string>
    12 #include <math.h>
    13 using namespace std;
    14 typedef long long LL;                   
    15 const int INF = 1<<30;
    16 const int N = 1000 + 10;
    17 vector<int> g[N];
    18 int dfs_clock,dfn[N],low[N],cntCut;
    19 void init(int n)
    20 {
    21     cntCut = dfs_clock = 0;
    22     for(int i=0; i<=n; ++i)
    23     {
    24         dfn[i] = low[i] = 0;
    25         g[i].clear();
    26     }    
    27 }
    28 //重边对割边有影响,比如有2--3  ,2--3两条边,那么边2--3就不是割边,因为去掉还是连通的,所以要判断一下
    29 void tarjan(int u, int fa)
    30 {
    31     dfn[u] = low[u] = ++dfs_clock;
    32     bool flag = false;
    33     for(int i=0; i<g[u].size(); ++i)
    34     {
    35         int v = g[u][i];
    36         if(v==fa && !flag)//在这里判断有没有重边
    37         {
    38             flag = true;
    39             continue;
    40         }
    41         if(dfn[v]==0)
    42             tarjan(v,u);
    43         low[u] = min(low[u],low[v]);
    44         if(low[v] > dfn[u])//这里统计的是割边的数量,如果要记录割边,那么就标记边,或者把边入栈
    45             cntCut++;
    46     }
    47 }
    48 int main()
    49 {
    50     int n,m,i,u,v;
    51     while(scanf("%d%d",&n,&m)!=EOF)
    52     {
    53         init(n);
    54         for(i=0; i<m; ++i)
    55         {
    56             scanf("%d%d",&u,&v);
    57             g[u].push_back(v);
    58             g[v].push_back(u);
    59         }
    60         tarjan(1,-1);
    61         printf("%d
    ",cntCut);
    62     }
    63     return 0;
    64 }
    View Code

    点-双连通分量:如果任意两点存在两条点不重复的路径,那么就说这个图是点-双连通的。点-双连通的极大子图称为双连通分量

    双连通分量之间的分界点是割点。而且双连通分量不可能分布在树根结点两端。所以我们将边入栈,当遇到割点时,就将边出栈,直到有边等于当前边。就跳出

    问题:重边对该算法有影响吗? 没有影响

      1 #include <stdio.h>
      2 #include <string.h>
      3 #include <stdlib.h>
      4 #include <algorithm>
      5 #include <iostream>
      6 #include <queue>
      7 #include <stack>
      8 #include <vector>
      9 #include <map>
     10 #include <set>
     11 #include <string>
     12 #include <math.h>
     13 using namespace std;
     14 typedef long long LL;                   
     15 const int INF = 1<<30;
     16 const int N = 1000 + 10;
     17 struct Edge
     18 {
     19     int u,v;
     20     Edge(){};
     21     Edge(int u, int v)
     22     {
     23         this->u = u;
     24         this->v = v;
     25     }
     26 };
     27 vector<int> g[N],bcc[N];
     28 int bccno[N],dfn[N],low[N],dfs_clock,cnt;
     29 stack<Edge> st;
     30 void init(int n)
     31 {
     32     cnt = dfs_clock = 0;
     33     for(int i=0; i<=n; ++i)
     34     {
     35         bccno[i] = low[i] = dfn[i] = 0;
     36         bcc[i].clear();
     37         g[i].clear();
     38     }    
     39 }
     40 void tarjan(int u, int fa)
     41 {
     42     dfn[u] = low[u] = ++dfs_clock;
     43     for(int i=0; i<g[u].size(); ++i)
     44     {
     45         int v = g[u][i];
     46         if(dfn[v]==0)
     47         {
     48             st.push(Edge(u,v));
     49             tarjan(v,u);
     50             low[u] = min(low[u],low[v]);
     51             if(low[v] >= dfn[u])//如果这个点是割点,那么先前入栈的一些边是属于一个双连通分量的
     52             {
     53                 Edge x;
     54                 cnt++;
     55                 for(;;)
     56                 {
     57                     x = st.top();
     58                     st.pop();
     59                     if(bccno[x.u] != cnt)
     60                     {
     61                         bccno[x.u] = cnt;
     62                         bcc[cnt].push_back(x.u);
     63                     }
     64                     if(bccno[x.v] != cnt)
     65                     {
     66                         bccno[x.v] = cnt;
     67                         bcc[cnt].push_back(x.v);
     68                     }
     69                     if(x.u==u && x.v==v)
     70                         break;
     71                 }
     72             }
     73         }    
     74         else if(v!=fa && dfn[v] < dfn[u])
     75         {
     76             st.push(Edge(u,v));
     77             low[u] = min(low[u],dfn[v]);
     78         }
     79         
     80     }
     81 }
     82 int main()
     83 {
     84     int n,m,i,u,v;
     85     while(scanf("%d%d",&n,&m)!=EOF)
     86     {
     87         init(n);
     88         for(i=0; i<m; ++i)
     89         {
     90             scanf("%d%d",&u,&v);
     91             g[u].push_back(v);
     92             g[v].push_back(u);
     93         }
     94         tarjan(1,-1);
     95         for(i=1; i<=cnt; ++i)
     96         {
     97             printf("bcc %d has vertex:",i);
     98             while(!bcc[i].empty())
     99             {
    100                 printf("%d ",bcc[i].back());
    101                 bcc[i].pop_back();
    102             }
    103             puts("");
    104         }
    105     }
    106     return 0;
    107 }
    View Code

    边-双连通分量:如果任意两点存在两条边不重复的路径,那么就说这个图是边-双连通的,边-双连通的极大子图成为边双连通分量。

    边双连通分量的分界点是割边。双连通分量可以分布在根结点的两端。所以不能在for(int i=0; i<g[u].size(); ++i) 这个循环里面判断割边,而要在外面递归返回时判断

    问题:重边对该算法有影响吗?有影响,就好像影响割边算法一样

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <stdlib.h>
     4 #include <algorithm>
     5 #include <iostream>
     6 #include <queue>
     7 #include <stack>
     8 #include <vector>
     9 #include <map>
    10 #include <set>
    11 #include <string>
    12 #include <math.h>
    13 using namespace std;
    14 typedef long long LL;                   
    15 const int INF = 1<<30;
    16 const int N = 1000 + 10;
    17 vector<int> g[N];
    18 stack<int> st;
    19 int dfn[N],low[N],dfs_clock,bccno[N],cnt;
    20 void init(int n)
    21 {
    22     cnt = dfs_clock = 0;
    23     for(int i=0; i<n; ++i)
    24     {
    25         dfn[i] = low[i] = 0;
    26         bccno[i] = 0;
    27         g[i].clear();
    28     }
    29 }
    30 
    31 void tarjan(int u, int fa)//边双连通分量可能分布在某子树树根的两个分支上
    32 {
    33     low[u] = dfn[u] = ++dfs_clock;
    34     st.push(u);
    35     bool flag = 0;
    36     for(int i=0; i<g[u].size(); ++i)
    37     {
    38         int v = g[u][i];
    39         if(v==fa && !flag) //重边对边连通分量的判断有影响,所以要判断一下
    40         {
    41             flag = true;
    42             continue;
    43         }
    44         if(dfn[v]==0)
    45             tarjan(v,u);
    46         low[u] = min(low[u],low[v]);
    47     }
    48     if(dfn[u]==low[u])//说明这个点的所有后代都没有连回自己祖先的边,
    49     {
    50         cnt++;
    51         for(;;)
    52         {
    53             int x = st.top();
    54             st.pop();
    55             bccno[x] = cnt;
    56             if(x==u)
    57                 break;
    58         }
    59     }
    60 }
    61 int main()
    62 {
    63     int n,m,i,u,v;
    64     while(scanf("%d%d",&n,&m)!=EOF)
    65     {
    66         init(n);
    67         for(i=0; i<m; ++i)
    68         {
    69             scanf("%d%d",&u,&v);
    70             g[u].push_back(v);
    71             g[v].push_back(u);
    72         }
    73         tarjan(1,-1);
    74         for(i=1; i<=n; ++i)
    75             printf("vexter %d belong to bcc %d
    ",i,bccno[i]);
    76     }
    77     return 0;
    78 }
    View Code

     问添加最少多少条边,使得整个图边-双连通, 首先求出边-双连通分量,然后缩点,最后变成一棵树,那么要加的边 就是(叶子结点+1)/2

    强连通分量:如果有向图的任意两点可以,那么就说这个图是强连通的,一个图的极大子图是强连通的,那么就说这个子图是强连通分量

    对于一个scc,我们要判断哪个点是该scc最先被发现的点,然后将后来发现的点出栈,知道遇到这个点。 那么出栈的点都属于一个强连通分量

    问题:重边对该算法有影响吗?没有影响

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <stdlib.h>
     4 #include <algorithm>
     5 #include <iostream>
     6 #include <queue>
     7 #include <stack>
     8 #include <vector>
     9 #include <map>
    10 #include <set>
    11 #include <string>
    12 #include <math.h>
    13 using namespace std;
    14 typedef long long LL;                   
    15 const int INF = 1<<30;
    16 const int N = 1000 + 10;
    17 vector<int> g[N];
    18 stack<int> st;
    19 int cnt,dfs_clock,dfn[N],low[N],sccno[N];
    20 void init(int n)
    21 {
    22     cnt = dfs_clock = 0;
    23     for(int i=0; i<=n; ++i)
    24     {
    25         dfn[i] = low[i] = sccno[i] = 0;
    26         g[i].clear();
    27     }
    28 }
    29 
    30 void tarjan(int u, int fa)
    31 {
    32     dfn[u] = low[u] = ++dfs_clock;
    33     st.push(u);
    34     for(int i=0; i<g[u].size(); ++i)
    35     {
    36         int v = g[u][i];
    37         if(dfn[v]==0)
    38         {
    39             tarjan(v,u);
    40             low[u] = min(low[u],low[v]);
    41         }    
    42         else if(sccno[v]==0)//因为有向图存在横插边,不能用横插边来更新low[u]
    43         {
    44             low[u] = min(low[u],low[v]);
    45         }
    46     }
    47     //同样,因为强连通分量可以分布在根结点的两个分支上,所以在递归返回的时候调用
    48     if(low[u] == dfn[u])
    49     {
    50         cnt++;
    51         for(;;)
    52         {
    53             int x = st.top();
    54             st.pop();
    55             sccno[x] = cnt;
    56             if(x==u)
    57                 break;
    58         }
    59     }
    60 }
    61 int main()
    62 {
    63     int n,m,i,u,v;
    64     while(scanf("%d%d",&n,&m)!=EOF)
    65     {
    66         init(n);
    67         for(i=0; i<m; ++i)
    68         {
    69             scanf("%d%d",&u,&v);
    70             g[u].push_back(v);
    71         }
    72         tarjan(1,-1);
    73         for(i=1; i<=n; ++i)
    74             printf("vertex %d belong to scc %d
    ",i,sccno[i]);
    75     }
    76     return 0;
    77 }
    View Code
  • 相关阅读:
    爬虫学习---美丽汤
    爬虫学习--使用百度api---天气
    爬虫学习----案例
    爬虫学习---模拟提交
    爬虫学习---糗事百科
    爬虫学习----pattern
    Android:控件AutoCompleteTextView 客户端保存搜索历史自动提示
    Android:res之selector背景选择器
    安卓--selector简单使用
    【Android基础】android shape详解(二)
  • 原文地址:https://www.cnblogs.com/justPassBy/p/4442144.html
Copyright © 2011-2022 走看看