当前位置:   article > 正文

图论——强连通分量详解!

强连通分量

写在前面:

本篇主要内容:

  • 强连通分量等概念
  • Tarjan算法的过程与实现

强连通分量等概念:

首先我们要明白上面是连通

连通:

在一张图中任意两个点能互相到达。(举个例子)

所以我们称上面的这个图是一个连通图! 

接着我们在来理解什么是强连通

强连通:

若一张有向图的节点两两互相可达,则称这张图是 强连通的。

和连通图的唯一不同就是连通图是向图,而强连通是向图。(再来个栗子)

 那明白了强连通,再看看什么是强连通分量

强连通分量:

首先一张图很可能强连通图,但是它的子图可能是强强连通图,那我们称该子图原图强连通分量。(额。。。再给给栗子)

例如上的图被框起来的每一个子图就是原图(整张图)的强连通分量! 

ok到这里有关强连通分量等概念就讲完了。终于可以讲如何求强连分量了。

Tarjan算法的过程与实现:

过程:

我们考虑 DFS 生成树与强连通分量之间的关系。

若该子图是强连通图,那么从这个子图的根节点出发必定那再回到根。

说的专业一点就是:如果结点 u 是某个强连通分量在搜索树中遇到的第一个结点,那么这个强连通分量的其余结点肯定是在搜索树中以 u 为根的子树中。结点 u 被称为这个强连通分量的根。

具体地,在这个查找的过程中,可以对经过的结点标记,当发现某一节点连向的点正好已经被标记过,则说明找到了一条回路,而这个回路上的所有点构成一个强连通分量。

为了保存这个强连通分量,我们需要知道这条路上有哪些点,而此时,栈就是一种适合该算法的数据结构。对于每次搜索的点,我们都加入栈中,遇到回路时,在把栈中的元素逐个弹出,记录它们的起始结点,直到栈中弹出的元素正好是起始结点时,结束弹栈,继续搜索其它强连通分量。在这个过程中,所有的点和都有的边都被遍历了一次,所以最终的时间复杂度为O ( N + E ) 

——图论——强连通分量(Tarjan算法)_上总介的博客-CSDN博客

额。。。学习了一下dalao

实现:

在 Tarjan 算法中为每个结点 u 维护了以下几个变量:

  1. dfn[u]:深度优先搜索遍历时结点 u 被搜索的次序。(就是时间戳)
  2. low[u]:在 u 的子树中能够回溯到的最早的已经在栈中的结点。
  3. subtree[u]:表示以 u 为根的子树。
  4. instack:表示是否在栈中的数组。

再来n个栗子:

 

 

不好意思图画太大了所以截的有点模糊,呃呃呃将就着看吧!

 好吧我画累了,上面这张图是最终版。。。(啊!创作不容易点个赞吧QA)

再说明一点:

对于一个连通分量图,我们很容易想到,在该连通图中有且仅有一个 u 使得 dfn[u]=low[u]。该结点一定是在深度遍历的过程中,该连通分量中第一个被访问过的结点,因为它的 dfn 和 low 值最小,不会被该连通分量中的其他结点所影响。

因此,在回溯的过程中,判定 dfn[u]=low[u] 是否成立,如果成立,则栈中 u 及其上方的结点构成一个 SCC(强连通分量)。

好好体会一下。

代码:

终于啊!写了5个小时了.........

伪代码:

  1. tarjan(u){
  2. dfn[u]=low[u]=++Index
  3. for each(u,v) in E
  4. if(v is not visted){
  5. tarjan(v)
  6. low[u]=min(low[u],low[v])
  7. }
  8. else if(v in s)
  9. low[u]=min(low[u],dfn[v])
  10. if(dfn[u]==low[u])
  11. repeat
  12. v=stack.pop
  13. print v
  14. until(u==v)
  15. }

 c++Tarjan模板:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int n,m,cnt,cntb;
  4. vector<int> edge[101];
  5. vector<int> belong[101];
  6. bool instack[101];
  7. int dfn[101],low[101];
  8. stack<int> s;
  9. void Tarjan(int u){
  10. ++cnt;
  11. dfn[u]=low[u]=cnt;
  12. s.push(u);
  13. instack[u]=true;
  14. for(int i=0;i<edge[u].size();i++){
  15. int v=edge[u][i];
  16. if(!dfn[v]){
  17. Tarjan(v);
  18. low[u]=min(low[u],low[v]);
  19. }
  20. else if(instack[v]){
  21. low[u]=min(low[u],dfn[v]);
  22. }
  23. }
  24. if(dfn[u]==low[u]){
  25. ++cntb;
  26. int node;
  27. do{
  28. node=s.top();
  29. s.pop();
  30. instack[node]=false;
  31. belong[cntb].push_back(node);
  32. }while(node!=u);
  33. }
  34. }
  35. int main(){
  36. scanf("%d%d",&n,&m);
  37. for(int i=1;i<=m;i++){
  38. int u,v;
  39. scanf("%d%d",&u,&v);
  40. edge[u].push_back(v);
  41. }
  42. Tarjan(1);
  43. printf("id :");
  44. for(int i=1;i<=n;i++){
  45. printf("%d ",i);
  46. }
  47. printf("\n");
  48. printf("dfn :");
  49. for(int i=1;i<=n;i++){
  50. printf("%d ",dfn[i]);
  51. }
  52. printf("\n");
  53. printf("low :");
  54. for(int i=1;i<=n;i++){
  55. printf("%d ",low[i]);
  56. }
  57. printf("\n");
  58. for(int i=1;i<=cntb;i++){
  59. printf("SCG %d :",i);
  60. for(int j=0;j<belong[i].size();j++){
  61. printf("%d ",belong[i][j]);
  62. }
  63. printf("\n");
  64. }
  65. return 0;
  66. }

可以试试样例:

  1. 7 11
  2. 1 2
  3. 2 3
  4. 2 5
  5. 2 4
  6. 3 5
  7. 3 7
  8. 7 5
  9. 5 6
  10. 6 7
  11. 4 1
  12. 4 6

运行结果:

例题:

【模板】缩点 - 洛谷

题目描述:

给定一个 n 个点 m 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。

允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

题解:

根据题目意思,我们只需要找出一条点权最大的路径就行了,不限制点的个数。那么考虑对于一个环上的点被选择了,一整条环是不是应该都被选择,这一定很优,能选干嘛不选。很关键的是题目还允许我们重复经过某条边或者某个点,我们就不需要考虑其他了。因此整个环实际上可以看成一个点(选了其中一个点就应该选其他的点)——luogu题解

代码(有注释):

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int N=1e4+10,M=1e5+10;
  4. struct point{
  5. int nxt;
  6. int to;
  7. int from;
  8. }e_1[M],e_2[M];
  9. int val[N];//存储点权
  10. int head_1[N],head_2[N];
  11. int cnt_1,cnt_2;
  12. int n,m;//点数,边数
  13. int dfn[N],low[N];
  14. int sta[N],top;//栈
  15. int ind;//遍历顺序,时间戳
  16. int inDegree[N];//入度
  17. int d[N];//以i为结尾的路径值
  18. int sd[N];//每个结点在连通分量中的根结点
  19. bool vis[N];
  20. int read(){
  21. int x=0,w=1;
  22. char ch=0;
  23. while(ch<'0' || ch>'9'){
  24. if(ch=='-') w=-1;
  25. ch=getchar();
  26. }
  27. while(ch>='0' && ch<='9'){
  28. x=x*10+(ch-'0');
  29. ch=getchar();
  30. }
  31. return x*w;
  32. }
  33. void add(int x,int y){
  34. e_1[++cnt_1].from=x;
  35. e_1[cnt_1].to=y;
  36. e_1[cnt_1].nxt=head_1[x];
  37. head_1[x]=cnt_1;
  38. }
  39. void Tarjan(int x){
  40. dfn[x]=low[x]=++ind;
  41. sta[++top]=x;
  42. vis[x]=1;
  43. for(int i=head_1[x];i;i=e_1[i].nxt){
  44. int y=e_1[i].to;
  45. if(!dfn[y]){
  46. Tarjan(y);
  47. low[x]=min(low[x],low[y]);
  48. }
  49. else if(vis[y]){
  50. low[x]=min(low[x],dfn[y]);
  51. }
  52. }
  53. if(low[x]==dfn[x]){
  54. int y;
  55. while(y=sta[top--]){
  56. sd[y]=x;
  57. vis[y]=0;
  58. if(y==x) break;
  59. val[x]+=val[y];//把连通分量中的其他结点的权值加到根结点上
  60. }
  61. }
  62. }
  63. int ask(){
  64. queue<int> q;
  65. for(int i=1;i<=n;i++){
  66. if(i==sd[i] && inDegree[i]==0){
  67. q.push(i);
  68. d[i]=val[i];
  69. }
  70. }
  71. int u,v;
  72. while(!q.empty()){
  73. u=q.front();
  74. q.pop();
  75. for(int i=head_2[u];i;i=e_2[i].nxt){
  76. v=e_2[i].to;
  77. d[v]=max(d[v],d[u]+val[v]);
  78. inDegree[v]--;
  79. if(inDegree[v]==0){
  80. q.push(v);
  81. }
  82. }
  83. }
  84. int ans=0;
  85. for(int i=1;i<=n;i++){
  86. ans=max(ans,d[i]);
  87. }
  88. return ans;
  89. }
  90. int main(){
  91. n=read();m=read();
  92. for(int i=1;i<=n;i++){
  93. val[i]=read();
  94. }
  95. //建图
  96. int u,v;
  97. for(int i=1;i<=m;i++){
  98. u=read();v=read();
  99. add(u,v);
  100. }
  101. //Tarjan
  102. for(int i=1;i<=n;i++){
  103. if(!dfn[i])
  104. Tarjan(i);
  105. }
  106. //重新建图
  107. for(int i=1;i<=m;i++){
  108. u=sd[e_1[i].from];
  109. v=sd[e_1[i].to];
  110. if(u!=v){
  111. e_2[++cnt_2].from=u;
  112. e_2[cnt_2].to=v;
  113. e_2[cnt_2].nxt=head_2[u];
  114. head_2[u]=cnt_2;
  115. inDegree[v]++;
  116. }
  117. }
  118. printf("%d",ask());
  119. return 0;
  120. }

 总结:

就讲这么多,平时练习多注意vector与链式前向星的转换。

今宵东方不见日,总有夜尽天明时。加油拜拜~~~

哦对了别忘了点赞!(特意标红。。。)

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号