首页 > 其他分享 >图的连通性问题

图的连通性问题

时间:2023-01-27 16:45:39浏览次数:37  
标签:连通 连通性 int tarjan 问题 dfn low 节点

\(dfs\) 树及其他概念

image

在这样一棵由在图上进行 \(dfs\) 所形成的 \(dfs\) 树中,我们注明了三种边。
黑色的边为 树边
绿色的边为 前向边,由一个节点指向它子树上的节点。
红色的边为 返祖边,由子树上的节点指向其父节点。这条边上的终点比这条边上的起点先被访问。
蓝色的边为 横叉边,由一个节点指向 \(dfs\) 树上既非其子节点,又非其父节点的点。这条边的终点比起点先被访问。
后三种边统称为非树边

小结论1:无向图中没有横叉边。在给出的这个 \(dfs\) 树中,我们假设所有的边都是无向边,那么当搜索到 \(7\) 节点时,显然可以继续搜索 \(9\) 节点,\(9\) 节点因此成为了 \(7\) 节点的子节点。因此不会存在横叉边。

小结论2:如果点 \(u\) 是某个强连通分量在 \(dfs\) 中第一次遇到的点,那么这个强连通分量的其它点必定在 \(dfs\) 树上 \(u\) 的子树中。

强连通性

首先,强连通性只是针对有向图的说法。如果在一个有向图中,从 \(u\) 出发能到 \(v\),且从 \(v\) 出发能到 \(u\),那么我们就称 \(u\) 和 \(v\) 强连通。强连通性满足传递性自反性对称性。我们可以根据点与点是否是强连通的将有向图中的节点划分成几个类,每个类中的节点两两强连通。这样的一个类我们称作强连通分量

如何求出有向图中的强连通分量?这里引入 \(tarjan\) 算法。

在这个算法中,最核心的是 \(dfn\) 和 \(low\) 这两个数组。\(dfn[i]\) 表示 \(dfs\) 搜索到该节点的时间,而 \(low[i]\) 表示在 \(i\) 节点及其子树中所有节点,只走一条非树边所能到达的最早的时间戳。显然,\(low\) 数组可以通过递推得到。

每搜索到一个新的节点,我们都把这个点扔到栈里去。我们假设 \(v\) 为当前节点 \(u\) 经过一条边——树边也好,非树边也好,所到达的节点。假如点 \(v\) 为 \(u\) 子树上的节点,那么由小结论2可知,\(v\) 能到的点 \(u\) 肯定能到,如此自底向上地进行更新,因此此时有:

\[low[u] = min(low[u],low[v]) \]

假设 \(v\) 没在 \(u\) 的子树上,那么就这样更新:

\[low[u] = min(low[u],dfn[v]) \]

当我们将 \(low[u]\) 算出来后,只需比较一下 \(low[u]\) 是否等于 \(dfn[u]\),如果二者相等,说明其子树里的节点无论如何也不能到达比 \(u\) 更早的节点。那么就反复弹栈,直到弹出 \(u\) 为止,弹出的所有节点为一个强连通分量。我们可以利用它来进行缩点

void tarjan(int u){
	low[u] = dfn[u] = ++tim;
	node[++node[0]] = u;
	vis[u] = 1;
	for(int i = head[u]; i != 0; i = edge[i].next){
		int v = edge[i].v;
		if(!dfn[v]){
			tarjan(v);
			low[u] = min(low[u],low[v]);
		}
		else if(vis[v]){
			low[u] = min(low[u],dfn[v]);
		}
	}
	if(low[u] == dfn[u]){
		++cnt;
		while(node[node[0] + 1] != u){
			color[node[node[0]]] = cnt;
			sum[cnt] += a[node[node[0]]];
			vis[node[node[0]--]] = 0;
		}
	}
}

另一种算法——\(Kosaraju\)。

重连通性a

边双连通:

对于 \(u\) \(v\) 两个节点,如果任意删除一条边,两个节点仍然连通,那么我们称这两个点边双连通。具有传递性自反性对称性

点双连通:

对于 \(u\) \(v\) 两个节点,如果任意删除除他们以外的一个节点,两个节点仍然相连通,那么我们称这两个点点双连通

割点:

删除一个点后,总连通分量数增加,就称这个点为割点。

割边(桥):

删除一条边后,总连通分量数增加,称这个点为割边。

先看如何求割点,同样的,这也能用 \(tarjan\) 算法来实现。

求割点的 \(tarjan\) 中,我们不需要栈。对于非根节点的点 \(u\),假如它有一个儿子 \(v\),使得 \(low[v] >= dfn[u]\),那么将 \(u\) 割掉后,其子树内的节点就无法到达更早的节点, \(u\) 便为 割点。如果是在无向图上求割点,需要注意在更新 \(low[v]\) 时,枚举的边的终点不能为 \(u\)。

对于根节点来说,如果其儿子数量大于 \(1\),割掉后 \(dfs\) 就会分裂,此时根节点也符合割点。

void tarjan(int u,int fa){
	low[u] = dfn[u] = ++tim;
	int kid = 0;
	for(int i = head[u]; i; i = edge[i].next){
		int v = edge[i].v;
		if(!dfn[v]){
			tarjan(v,fa);
			low[u] = min(low[u],low[v]);
			if(low[v] >= dfn[u] && u != fa)ans.push_back(u);
			if(u == fa)kid++;
		}
		else low[u] = min(low[u],dfn[v]);
	}
	if(kid >= 2 && u == fa)ans.push_back(u);
}

再看怎么求割边,仍是 \(tarjan\)。

原理类似求割点,只需将判断条件改为 \(low[v] > dfn[u]\) 即可。如果满足这个条件, \(u-v\) 这条边就是一条割边。如果是在无向图上求桥,需要注意在更新 \(low[v]\) 时,枚举的边的终点不能为 \(u\)。

无向图求桥:

void tarjan(int u,int fa){
	low[u] = dfn[u] = ++tim;
	int k;
	for(int i = head[u]; i; i = edge[i].next){
		int v = edge[i].v;
		if(v == fa){
			k = i;
			continue;
		}
		if(!dfn[v]){
			tarjan(v,u);
			low[u] = min(low[u],low[v]);
			if(low[v] > dfn[u])bridge[++bridge[0]] = i;
		}
		else low[u] = min(low[u],dfn[v]);
	}
	if(low[u] > dfn[fa])bridge[++bridge[0]] = k;
}

如此,求点双、边双就变得简单了。

把所有桥去掉,图中的每个连通块都是(极大)边双连通分量。

求点双时,仍用一个栈记录访问的节点,如果发现 \(u\) 的一个子节点 \(v\) 满足 \(u\) 是一个割点,那么反复弹栈直到弹出 \(v\),弹出的这一堆和点 \(u\) 为一个(极大)点双连通分量。

void tarjan(int u){
	low[u] = dfn[u] = ++tim;
	sta[++sta[0]] = u;
	for(int i = head[u]; i; i = edge[i].next){
		int v = edge[i].v;
		if(!dfn[v]){
			tarjan(v);
			low[u] = min(low[u],low[v]);
			if(low[v] >= dfn[u]){
				++cnt;
				while(sta[sta[0] + 1] != v){
					int k =	sta[sta[0]--];
					ans[cnt].push_back(k);
				}
				ans[cnt].push_back(u);
			}
		}
		else low[u] = min(low[u],dfn[v]);
	}
}

一个结论:割点至少属于两个极大点双连通分量,非割点只属于某一个极大点双连通分量。

标签:连通,连通性,int,tarjan,问题,dfn,low,节点
From: https://www.cnblogs.com/CZ-9/p/17068968.html

相关文章

  • 二叉树公共祖先问题
    packagedayone.tre;/****o1和o2为head二叉树中的点,找出o1和02的最近公共祖先*/publicclassTest{publicstaticNodelowestAncestor(Nodehead,Nodeo......
  • 一个经典的多项式问题
    有的时候会遇到这样的问题:给定多项式\(F(x)\),求\(F(e^x)\)的前\(n\)个项。列出式子:\[\sum_{i=0}^{n}a_i\sum_{j=0}\dfrac{(ix)^j}{j!}\]我们这里将\(\dfrac{x......
  • 【ElementPlus】el-menu侧边垂直菜单折叠后图标会消失的问题解决
    首先上代码<template><templatev-for="subIteminmenuList":key="subItem.path"><!--有子菜单--><el-sub-menuv-if="subItem.children&&s......
  • 关于#c++#的问题,如何解决
    提问: 又出了另一个错:```c++#include<iostream>#include<stdio.h>#include<algorithm>intco=0;usingnamespacestd;charb[10000000];intmain(){......
  • 便宜的小程序有哪些问题
    小程序最大的优点就是便宜有利于中小企业起步腾讯小程序提供很多免费服务,比自己开发APP省的多。免费的客服,免费的GPS,在苹果手机上不需要100美元。便宜没好货有问题必须......
  • windows2003 的安装以及安装时遇到的问题
    windows2003的安装以及安装时遇到的问题简介:WindowsServer2003是微软于2003年3月28日发布的基于WindowsXP/NT5.1开发的服务器操作系统,并在同年4月底上市。WindowsServ......
  • Python 三维绘图问题
    提问: 各位,本人刚刚才接触Python。现在有个问题在于,我有一组数据想要去将变成三维曲面图,网上教程多是曲面上的点Z用XY来表示,但是我这个数据是单纯的测量数据,并没有什么公......
  • 关于Python 面向对象寻值的问题. How the number be found in the OOP in Python
    今天在看Python面向对象的时候看到了一个很有意思的问题Today.WhenilearningtheOOPinpython,IfoundaveryinterestingQuestionthathowanumberbefound......
  • No Cortex-M Device found in JTAG chain 问题的解决
     出现下载失败,被坑了了两次记录一下出现原因总结1、Keil版本太低,程序下载不了单片机,建议卸载重装,会解决2、升级完Keil但是一段时间之后又出现问题,解决方法(1)首先打开S......
  • redis 缓存引发的头疼问题
    缓存穿透某个key缓存没有,数据库也没有。一般这种情况发生了用户恶意请求或者攻击。造成一直不停查库解决方案最顶层拦截,不合理的id直接打回去或者布隆过滤器db如果差不多,......