当前位置:   article > 正文

【图论】无向图连通性(tarjan算法)_无向连通图

无向连通图

割边:dfn[u]<low[v]

割点:dfn[u]<=low[v] (若为根节点,要有两个v这样的点)

一.知识点:

1.连通

在图论中,连通性是指一个无向图中的任意两个顶点之间存在路径。如果对于图中的任意两个顶点 u 和 v,存在一条路径从 u 到 v,那么图被称为连通图。如果图不是连通的,那么它可以被分为多个连通分量,每个连通分量都是一个连通子图。

2.割点:

割点(Cut Vertex),也称为关节点或割顶,是指在无向图中,如果移除该顶点及其相连的边,会导致图不再连通,那么该顶点就被称为割点。

3.割边:

割边(Cut Edge),也称为,是指在无向图中,如果移除该边,会导致图不再连通,那么该边就被称为割边。

割边在图中起到了连接不同连通分量的作用,其移除会导致图的连通性发生变化。

 4.tarjan算法:(选择性阅读)

 Tarjan算法是一种用于寻找有向图中强连通分量(Strongly Connected Components,简称SCC)的算法,由Robert Tarjan在1972年提出。强连通分量是指在有向图中,任意两个顶点之间存在双向路径。

Tarjan算法使用深度优先搜索(DFS)来遍历图,并通过维护一个栈和一些辅助数据结构来识别强连通分量。算法的基本思想是通过DFS遍历图中的每个顶点,并为每个顶点记录其访问次序(Discovery Time)和能够回溯到的最早的祖先顶点(Lowest Ancestor)。通过这些信息,可以识别出具有相同祖先的顶点集合,即一个强连通分量。

Tarjan算法的步骤如下:

  1. 对图中的每个顶点进行深度优先搜索(DFS)遍历。
  2. 在DFS遍历的过程中,为每个顶点记录其访问次序和最早祖先顶点。
  3. 将已访问的顶点入栈。
  4. 当DFS遍历回溯到一个顶点时,检查该顶点的最早祖先顶点。如果最早祖先顶点是自身,则将栈中的顶点弹出,并将这些顶点构成一个强连通分量。
  5. 重复步骤3和步骤4,直到遍历完所有的顶点。

Tarjan算法的时间复杂度为O(V+E),其中V是顶点数,E是边数。它是一种高效的算法,常被用于解决与强连通分量相关的问题,如图的缩点、强连通分量的数量和结构等。

总之,Tarjan算法是一种用于寻找有向图中强连通分量的算法,通过DFS遍历和栈的运用,可以高效地找到图中的所有强连通分量。


二.讲解 

在此之前,先介绍两个数组;

int dfn[];里面存放访问顺序(时间戳);

int low[];里面存放追溯值(即祖先节点最小的dfn)

(1)割边

tarjan提出:(证明可以自行百度)

当dfn[u]<low[v]时,连接这两条点的边为割边(重边要特殊处理,后面介绍)

(2)割点

tarjan提出:(证明可以自行百度)

当dfn[u]<=low[v]时,u这个点为割点(若为根节点,要有两个v这样符合条件的点)


三.割边

(1)题目

P1656 炸铁路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述:

找出割边

输入:

第一行输入两个整数n和m,表示点和边的个数。

第i(2<=i<=2+m)行,每行输出两个数字,表示一条边的两个点。

输出:

割边

样例输入:

6 7
1 2
1 3
2 4 
2 5
3 4
4 5
4 6

样例输出:

4---6

(2)初代码 

  1. /*
  2. 6 7
  3. 1 2
  4. 1 3
  5. 2 4
  6. 2 5
  7. 3 4
  8. 4 5
  9. 4 6
  10. */
  11. #include<bits/stdc++.h>
  12. #define maxn 100005
  13. using namespace std;
  14. int n,m;
  15. struct Edge{
  16. int u,v,next;
  17. }edge[maxn<<1];
  18. int cnt,head[maxn];
  19. void add(int u,int v){
  20. edge[++cnt]=(Edge){u,v,head[u]}; head[u]=cnt;
  21. }
  22. int num,dfn[maxn],low[maxn];
  23. void tarjan(int u,int fa){
  24. dfn[u]=low[u]=++num;
  25. for(int i=head[u];i;i=edge[i].next){
  26. int v=edge[i].v;
  27. if(v==fa) continue;
  28. if(dfn[v]==0){
  29. tarjan(v,u);
  30. low[u]=min(low[u],low[v]);
  31. if(dfn[u]<low[v]){ //割边条件 ,若>则表示v不止和u相连
  32. cout<<u<<"----"<<v<<endl;
  33. }
  34. }else{
  35. low[u]=min(low[u],dfn[v]);
  36. }
  37. }
  38. }
  39. int main(){
  40. scanf("%d%d",&n,&m);
  41. int u,v;
  42. for(int i=1;i<=m;i++){
  43. scanf("%d%d",&u,&v);
  44. add(u,v); add(v,u);
  45. }
  46. tarjan(1,0);
  47. return 0;
  48. }

(3)bug与解答

1.若这张图有多个连通分量怎么办?

答:遍历即可

  1. for(int i=1;i<=n;i++){
  2. if(dfn[i]==0) tarjan(1,0);
  3. }

2.若有重边怎么办?结果显然不对。

答:只continue,第二次让这段代码运行

然后就无法满足 dfn[u]<low[v]条件了

  1. if(v==fa){
  2. k++; //防止重边
  3. if(k==1) continue;
  4. }

(4)最终代码

  1. #include<bits/stdc++.h>
  2. #define maxn 5005
  3. using namespace std;
  4. int n,m;
  5. struct Edge{
  6. int u,v,next;
  7. }edge[maxn<<1];
  8. int head[maxn],cnt;
  9. void add(int u,int v){
  10. edge[++cnt]=(Edge){u,v,head[u]}; head[u]=cnt;
  11. }
  12. int dfn[maxn],low[maxn],num;
  13. struct node{
  14. int u,v;
  15. }ans[maxn];
  16. int an;
  17. bool cmp(node a,node b){
  18. if(a.u!=b.u) return a.u<b.u;
  19. else return a.v<b.v;
  20. }
  21. void tarjan(int u,int fa){
  22. int k=0;
  23. dfn[u]=low[u]=++num;
  24. for(int i=head[u];i;i=edge[i].next){
  25. int v=edge[i].v;
  26. if(v==fa){
  27. k++;
  28. if(k==1) continue;
  29. }
  30. if(dfn[v]==0){
  31. tarjan(v,u);
  32. low[u]=min(low[u],low[v]);
  33. if(dfn[u]<low[v]){
  34. if(v<u) swap(u,v);
  35. ans[++an]=(node){u,v};
  36. }
  37. }else{
  38. low[u]=min(low[u],dfn[v]);
  39. }
  40. }
  41. }
  42. int main(){
  43. scanf("%d%d",&n,&m);
  44. for(int i=1;i<=m;i++){
  45. int u,v;
  46. scanf("%d%d",&u,&v);
  47. add(u,v);add(v,u);
  48. }
  49. for(int i=1;i<=n;i++){
  50. if(!dfn[i]) tarjan(i,0);
  51. }
  52. sort(ans+1,ans+an+1,cmp);
  53. for(int i=1;i<=an;i++){
  54. cout<<ans[i].u<<" "<<ans[i].v<<endl;
  55. }
  56. return 0;
  57. }

四.割点

其实只是微改动一下即可。其次就是可以优化一下。函数传参只需要传u,无需判断是否为父节点。因为不会影响结果。(自行参考代码推理)

再次强调:若为根节点,要有两个v这样的点!

参考代码:

  1. /*
  2. 6 7
  3. 1 2
  4. 1 3
  5. 2 4
  6. 2 5
  7. 3 4
  8. 4 5
  9. 4 6
  10. */
  11. #include<bits/stdc++.h>
  12. #define maxn 100005
  13. using namespace std;
  14. int n,m;
  15. struct Edge{
  16. int u,v,next;
  17. }edge[maxn<<1];
  18. int cnt,head[maxn];
  19. void add(int u,int v){
  20. edge[++cnt]=(Edge){u,v,head[u]}; head[u]=cnt;
  21. }
  22. int ans[maxn],an;
  23. int num,dfn[maxn],low[maxn],root;
  24. void tarjan(int u){
  25. dfn[u]=low[u]=++num;
  26. int flag=0;
  27. for(int i=head[u];i;i=edge[i].next){
  28. int v=edge[i].v;
  29. if(dfn[v]==0){
  30. tarjan(v);
  31. low[u]=min(low[u],low[v]);
  32. if(dfn[u]<=low[v]){ //割点条件
  33. flag++;
  34. if(u!=root || flag>1) ans[u]=1;
  35. }
  36. }else{
  37. low[u]=min(low[u],dfn[v]);
  38. }
  39. }
  40. }
  41. int main(){
  42. scanf("%d%d",&n,&m);
  43. int u,v;
  44. for(int i=1;i<=m;i++){
  45. scanf("%d%d",&u,&v);
  46. add(u,v); add(v,u);
  47. }
  48. //防止本来就有不连通的
  49. for(int i=1;i<=n;i++){
  50. if(dfn[i]==0){
  51. root=i;
  52. tarjan(i);
  53. }
  54. }
  55. for(int i=1;i<=n;i++){
  56. if(ans[i]) an++;
  57. }
  58. cout<<an<<endl;
  59. for(int i=1;i<=n;i++){
  60. if(ans[i])
  61. cout<<i<<" ";
  62. }
  63. return 0;
  64. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/891142
推荐阅读
相关标签
  

闽ICP备14008679号