首页 > 其他分享 >【学习笔记】无向图的连通性

【学习笔记】无向图的连通性

时间:2023-07-25 20:56:53浏览次数:32  
标签:连通 连通性 int 无向 笔记 割点 ++ dfn low

# 割点

**定义:** 在无向图连通图中,若把点 $x$ 删去后整个图就不连通了,则 $x$ 为割点(割顶)。

**朴素方法:** 每次删去一个点,然后判断图是否连通,时间复杂度为 $O(n(n+m))$。

**Tarjan 算法:**

$dfn_x$:$x$ 被 `dfs` 到的时间戳

$low_x$:在 $x$ 及以后被搜索的所有节点的 $low$ 值和这些节点能到达的节点的 $dfn$ 的最小值。

**算法流程:**

1. 从 $1$ 号点开始遍历全图,对于遍历到的点 $x$,记录它的 $dfn_x$ 并将 $low_x$ 的值赋为 $dfn_x$。
2. 接下来遍历 $x$ 的所有子儿子 $y$:
- 若 $y$ 被访问过,则 $low_x=\min(low_x,dfn_y)$。
- 若 $y$ 没有被访问过,则先遍历 $y$,接着 $low_x=\min(low_x,low_y)$。

**算法原理:**

若点 $x$ 不为割点,把所有没被访问过 $y$ 放在 $x$ 的“下方”,则必存在一条边从 $y$ 连到 $x$ 的“上方”且这条边在 $x$ 的“右边”(不然应该先访问到这条边,或者说访问顺序在 $x$ 之后),按照 $low$ 数组的定义,则所以的 $y$ 都满足 $low_y<dfn_x$。

相反,若 $x$ 为割点,则必有 $low_y\ge dfn_x$(注意,这里有等于号,因为若连到自己则仍为割点)。

**特例:** 若 $x$ 为根节点,则肯定存在 $low_y\ge dfn_x$,判断不出割点,需要特判一下:如果 $x$ 连接的联通块个数 $\ge 2$,则其断开后这几个联通块会分开,则 $x$ 为割点。

```cpp
void tarjan(int k, int rt) {
int cnt = 0;
dfn[k] = low[k] = ++s;
for (auto i : p[k]) {
if (!dfn[i]) {
cnt++;
tarjan(i, rt);
low[k] = min(low[k], low[i]);
if (k != rt && low[i] >= dfn[k]) {
if (!flag[k]) ans++;
flag[k] = true;
}
}
else low[k] = min(low[k], dfn[i]);
}
if (k == rt && cnt > 1) {
if (!flag[k]) ans++;
flag[k] = true;
}
}
```
**时间复杂度:**$O(n+m)$

# 割边
**定义:** 在无向连通图中,若删去某条边,这个图就不连通了,则称这条边是割边(桥)。

**Tarjan 算法:**

求割边的算法与求割点的算法差不多,只是如果一条从 $x$ 到 $y$ 的边为割边,则 $y$ 不存在边能到 $y$ 节点及其“上方”,即存在 $y$ 使 $low_y>dfn_x$(这里和割点有所区别)。

代码实现时要注意,这里的边是双向边,为了每条边只访问一次,可以使用邻接链表来存图,这样在边表中一条边 $e$ 的反向边为 $e\oplus1$,注意这样存图边的编号得从 $2$ 开始。

```cpp
void tarjan(int k) {
int cnt = 0;
dfn[k] = low[k] = ++s;
for (int i = head[k]; i; i = e[i].nxt) {
if (vis[i]) continue;
vis[i] = vis[i ^ 1] = true;
if (!dfn[e[i].to]) {
tarjan(e[i].to);
low[k] = min(low[k], low[e[i].to]);
if (low[e[i].to] > dfn[k])
bri[i] = bri[i ^ 1] = true;
}
else low[k] = min(low[k], dfn[e[i].to]);
}
}
```

**时间复杂度:**$O(n+m)$

# 点双连通分量(v-Dcc)
**定义:** 无向图中极大的(不能往外扩张)不存在割点的连通图(割点是指“局部”的割点),其任意两点之间都存在不经过重复点的两条路径。

**性质:** 一个割点可以存在于多个点双连通分量中,非割点只能存在于一个点双连通分量中,否则其就不是“分量”。

**求法:** 同样是 Tarjan 算法,不过要开一个栈记录:遍历到 $x$ 时便把 $x$ 压入栈,若 $x$ 为割点,则它通过当前点“吊”着的所有点都弹出($x$ 不弹出),而 $x$ 则是由“吊”着它的点弹出。有个注意点,到 $y$ 时只应弹出 $y$ 吊着的这一串,而不是把 $x$ 吊的所有点都弹出。

**特例:** 当 $x$ 为孤立点时,它自成一个点双连通分量。

```cpp
void tarjan(int k) {
q.push(k);
dfn[k] = low[k] = ++s;
int sum = 0;
for (auto i : p[k]) {
if (!dfn[i]) {
sum++;
tarjan(i);
low[k] = min(low[k], low[i]);
if (low[i] >= dfn[k]) {
cnt++;
int t;
do {
t = q.top();
ans[cnt].push_back(q.top());
q.pop();
}while (t != i);
ans[cnt].push_back(k);
}
}
else low[k] = min(low[k], dfn[i]);
}
if (k == rt && sum == 0) ans[++cnt].push_back(k);
}
```
**时间复杂度:**$O(n+m)$
# 边双连通分量
**定义:** 无向图中极大的(不能往外扩张)不存在割边的连通图,其任意两点之间都存在不经过重复边的两条路径。

**性质:** 割边可以不存在于任何边双连通分量中,非割边只能存在于一个边双连通分量中。

**求法:** 由于边双连通分量中不包含割边,那么就可以先跑一遍 Tarjan 标记出割边,然后 dfs,不经过割边跑出的联通块就是边双连通分量。

```cpp
//省略 Tarjan 函数
void dfs(int k)
{
ans[sum].push_back(k);
vis[k] = true;
for (int i = head[k]; i; i = e[i].nxt)
{
if (bri[i]) continue;
int v = e[i].to;
if (!vis[v]) dfs(v);
}
}
```

# 练习题

[ $\color{#9D3DCF}\texttt{SP2878 KNIGHTS - Knights of the Round Table}$](https://www.luogu.com.cn/problem/SP2878)

[ $\color{#3498DB}\texttt{P3469 [POI2008] BLO-Blockade}$](https://www.luogu.com.cn/problem/P3469)

[ $\color{#3498DB}\texttt{P2860 [USACO06JAN] Redundant Paths G}$](https://www.luogu.com.cn/problem/P2860)

标签:连通,连通性,int,无向,笔记,割点,++,dfn,low
From: https://www.cnblogs.com/zhuoyuxuan/p/17580980.html

相关文章

  • 笔记-交易圣经
    笔记-交易圣经通用原则一:思想准备最大逆境:没有最坏,只有更坏情绪指向:目标与期望失利:失败是必然随机市场:不确定性,不可预测性输得起才会赢:生存是第一要义风险管理:如上交易伙伴:防止自欺欺人财务边界:闲钱通用原则二:启蒙避免爆仓风险:有可能即是迟早会发生。没有杠杆减......
  • python无向图生成
    无向图生成与分析前言在计算机科学中,图是一种非常重要的数据结构,用于描述对象之间的关系。图由节点(顶点)和边组成,其中节点表示对象,边表示节点之间的关系。根据边的方向性,图可以分为有向图和无向图。本文将重点介绍无向图的生成与分析。无向图的定义和表示无向图是一种图形结构,其......
  • Numpy学习笔记之Numpy练习
    练习1:分别按照要求,生成一个一维数组、二维数组,并且查看其shapea1=np.array([1,2,'a','hello',[1,2,3],{'one':100,'two':200}])a2=np.array([list(range(6)),list('abcdef'),[True,False,True,False,True,True]])print(a1,'......
  • 2023长郡集训 动态规划笔记
    动态规划原理何为动态规划?动态规划(\(\text{Dynamicprogramming}\)),简称DP。DP并不是一种算法,与模拟、贪心一样,而是一种解决问题的方式。DP的基本思想为「将给定的问题拆分为一个个规模更小的子问题,直到子问题可以直接解决,返回/保存这个值,再根据方程一步步推出原本问题的答......
  • python教程 入门学习笔记 第1天
    初识python一、python语言简介:1、起源:1989年由荷兰的前谷歌程序员吉多.范罗苏姆(龟叔)创造,python的命名来源于英国电视喜剧MontyPython’sFlyingCircus飞行马戏团2、优势:python、Java、c这几种是世界最流行语言;用途广泛,被称为万能语言;语法简洁,上手简单;例如:print("hellowor......
  • typescripts学习笔记(三)
    typescripts学习笔记(三)-实现过程引言Typescript是一种由微软开发的开源编程语言,它是JavaScript的超集,可以在任何支持JavaScript的环境中运行。本篇文章将教你如何使用Typescript来创建一个简单的学习笔记应用。整体流程下面是整个实现过程的流程图:步骤描述步骤1......
  • [c/c++][考研复习笔记]内部排序篇学习笔记
    考研排序复习笔记插入排序#include<stdio.h>#include<stdlib.h>#defineMaxSize9//折半插入排序voidZBInsertSort(intA[],intn){ inti,j,high,low,mid; for(i=2;i<=n;i++){ A[0]=A[i]; low=1;high=i-1; while(low<=high){ mid=(low+high)/2......
  • Numpy学习笔记
    一、Numpy基础数据结构NumPy数组是一个多维数组对象,称为ndarray。其由两部分组成:①实际的数据②描述这些数据的元数据二、常见方法importnumpyasnpar=np.array([[[1,2,3,4,5,6,7],[1,2,3,4,5,6,7],[1,2,3,4,5,6,7]],[[1,2,3,4,5,6,7],[1,2,3,4,5,6,7],[1,2,3,4,5,......
  • 手写数字识别代码学习笔记
    图像预处理importtorchvision.transformsastransforms#定义数据预处理步骤【compose->组成】transform=transforms.Compose([transforms.Resize((128,128)),#将图像大小调整为128x128像素transforms.RandomCrop(100),#随机裁剪图像为10......
  • Redis Scan命令踩坑笔记
    前记大部分人在接触Redis时就都会了解到Redis是以单线程的形式处理用户命令,导致O(N)的命令有极大的几率会阻塞Redis,所以在使用Redis时需要放弃一些O(n)命令的使用,比如不要去使用KEYS命令而应该使用SCAN命令,然而SCAN命令也有一些坑。1.踩到的坑为了减少MySQL的压力,在部分变动比较少......