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\n",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\n",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\n",i,sccno[i]);
75     }
76     return 0;
77 }
View Code

 

posted @ 2015-04-20 17:27  justPassBy  阅读(734)  评论(0编辑  收藏  举报