当前位置: 首页 > news >正文

20250819 强连通分量,边双总结

连通性定义

  • 连通分量:无向图中所有节点相互连通的子图。

  • 弱连通分量:将有向图所有边视为无向边后,所有节点相互连通的子图。

  • 强连通分量:有向图中任意两个节点互相连通的子图。

  • 点双连通分量:连通子图中删除任一节点后,其余节点仍保持连通。

  • 边双连通分量:连通子图中删除任一条边后,其余节点仍保持连通。

强连通分量

一张有向图里,如果一张子图,它包含的节点互相连通并且极大,那么他就是极大强连通子图,也就是强连通分量

举个栗子(强连通分量)

假设有以下图:

1
2
3
4
5
6
7

该图有三个强连通分量,分别为{1,2,3}、{4,5,6}、{7}。注意,不能把7归入到第二个强连通分量,因为7和4、5、6并不互相连通。

DFS生成树(搜索树)

在有向图的DFS生成树中存在四种边类型:

  1. 树边:从父节点指向未被访问的子节点
  2. 返祖边:指向祖先节点的边
  3. 前向边:指向子树中的边
  4. 横叉边:指向已访问过的非祖先非子树节点

需要注意的是,前向边和横叉边仅存在于有向图的DFS树中,无向图中只会有树边和返祖边。

关于DFS生成树与强连通分量的关系:

在一个强连通分量中,若点u是最先被搜索到的节点,则该分量中其余所有节点都必然位于以u为根的子树内。

证明? 反证法

假设强连通分量中存在节点v不在u的子树中。由于u和v互相可达,u到v的路径上必须存在离开子树的边(横叉边或返祖边),这意味着这些边指向的节点已被访问。

但若u和v同属一个强连通分量,这些更早被访问的节点本应能访问到u,这与u是最先被搜索到的假设矛盾,成立。

缩点

找到强连通分量后,可以考虑缩点,也就是把一整个强连通分量压缩成一个点,这样就能使原图变成有向无环图。

可以发现,有向无环图绝不连通,就是不会再有强连通分量。

缩点时要注意原图中的边和新图中的边的对应关系。

举个栗子(缩点)

假设有以下图:

1
2
3
4
5
6
7

该图有三个强连通分量,分别为{1,2,3}、{4,5,6}、{7}。

缩点之后长这样:

1,2,3
4,5,6
7

是不是看起来非常的简洁又明了呢?这就是缩点的益处。

实现

void tarjan(int x){//强连通分量dfn[x]=low[x]=++cnt;q.push(x);for(int i=0;i<E[x].size();i++){int v=E[x][i];if(dfn[v]==0){tarjan(v);low[x]=min(low[x],low[v]);}else if(!gp[v]){low[x]=min(low[x],dfn[v]);}}if(low[x]==dfn[x]){gp[x]=++t;ts[t].push_back(x);while(q.top()!=x){gp[q.top()]=t;ts[t].push_back(q.top());q.pop();}q.pop();}
}
void tarjan(int x){//缩点dfn[x]=low[x]=++cnt;s.push(x);for(int i=0;i<E[x].size();i++){int v=E[x][i];if(!dfn[v]){tarjan(v);low[x]=min(low[x],low[v]);}else if(!gp[v]){low[x]=min(low[x],dfn[v]);}}if(low[x]==dfn[x]){k++;while(s.top()!=x){gps[k]+=D[s.top()];gp[s.top()]=k;s.pop();}gp[x]=k;dp[k]=gps[k]+=D[x];s.pop();			}		
}
int main(){for(int i=1;i<=n;i++){if(!dfn[i]){tarjan(i);} }for(int i=1;i<=n;i++){for(int j=0;j<E[i].size();j++){int v=E[i][j];if(gp[i]!=gp[v]){e[gp[i]].push_back(gp[v]);rd[gp[v]]++;}}}return 0;
}

边双连通分量

将强连通分量中的有向边改为无向边,即可得到边双连通分量

这是因为在强连通分量中,任意两点u和v都是相互可达的。当我们将有向边替换为无向边后,任意两点之间必然存在至少两条不同的路径。

需要注意的是,不要将重边合并,否则上述结论将不再成立。

实现

void dfs(int x,int fa){dfn[x]=low[x]=++cnt;s.push(x);for(auto v:E[x]){if(v.second==(fa^1))continue;if(!dfn[v.first]){dfs(v.first,v.second);low[x]=min(low[x],low[v.first]);}else{low[x]=min(low[x],dfn[v.first]);}}if(low[x]==dfn[x]){num++;while(s.top()!=x){ans[num].push_back(s.top());s.pop();					}ans[num].push_back(x);s.pop();}
}
int main(){for(int i=1;i<=n;i++){if(!dfn[i]){dfs(i,0);}}cout<<num<<endl;for(int i=1;i<=num;i++){cout<<ans[i].size();for(int j=0;j<ans[i].size();j++){cout<<" "<<ans[i][j];}cout<<endl;}return 0;
}
http://www.xdnf.cn/news/1328149.html

相关文章:

  • 从线性回归到神经网络到自注意力机制 —— 激活函数与参数的演进
  • 人工智能统一信息结构的挑战与前景
  • 比赛准备之环境配置
  • 进程间的通信1.(管道,信号)
  • LINUX 软件编程 -- 线程
  • 决策树(续)
  • LeetCode100-560和为K的子数组
  • 决策树1.1
  • 项目一系列-第5章 前后端快速开发
  • 项目管理.管理理念学习
  • react-quill-new富文本编辑器工具栏上传、粘贴截图、拖拽图片将base64改上传服务器再显示
  • LeetCode算法日记 - Day 16: 连续数组、矩阵区域和
  • 第4章 React状态管理基础
  • 算法训练营day56 图论⑥ 108. 109.冗余连接系列
  • 项目过程管理的重点是什么
  • Ansible 角色管理
  • 点大餐饮独立版系统源码v1.0.3+uniapp前端+搭建教程
  • GStreamer无线图传:树莓派到计算机的WiFi图传方案
  • GEO 优化专家孟庆涛:技术破壁者重构 AI 时代搜索逻辑
  • RESTful API 开发实践:淘宝商品详情页数据采集方案
  • Apache IoTDB:大数据时代时序数据库选型的技术突围与实践指南
  • 从0到1认识Rust通道
  • Redis-缓存-击穿-分布式锁
  • 无人机场景 - 目标检测数据集 - 山林野火烟雾检测数据集下载「包含VOC、COCO、YOLO三种格式」
  • 国产!全志T113-i 双核Cortex-A7@1.2GHz 工业开发板—ARM + FPGA通信案例
  • 如何免费给视频加字幕
  • Linux的ALSA音频框架学习笔记
  • Spring AOP 和 Spring 拦截器
  • LeetCode 100 -- Day2
  • JVM垃圾收集器