最近看了看类的相关题,感觉简单的题过于模板,但是对于难题的转化,如果对与这方面的概念不清楚,很难写,故总结一下。
PS:博客里部分内容会和离散数学中的图论知识有联系,如果没有了解过相关知识可能比较难理解。
下文所说的割点=关节点,割边=桥=关节边。
首先声明一下,名叫Tarjan的算法有三种,分别为
(1) 有向图的强联通分量类问题
(2) 无向图的双联通分量(求割点,桥)类问题
(3) 最近公共祖先(LCA)
这里前两类问题比较相似,而第三类是解决LCA公共祖先问题的算法,这篇帖子将不会涉及。
下面我来具体解释一下各种概念
首先,先来理解一下连通图的概念
连通图,顾名思义,他所有的点应该都是连通的,这里的连通对于有向图和无向图来说还是有一定区别的。
无向图
在一个无向图G中,若从顶点i到顶点j有路径相连(当然从j到i也一定有路径),则称i和j是连通的。如果图中任意两点都是连通的,那么这个无向图就是连通图,简单来说只要他的所有节点都在一起连着,没有任何一个节点与其他节点直接没有连边,那么他就是一个连通图。
有向图
如果G是有向图,那么连接i和j的路径中所有的边都必须同向。如果图中任意两点都是连通的,则称为强连通图(注意:有向图需要双向都有路径,如果i可以到j而j不可以到i,那么就不是强连通图)。
有强连通图,就一定还会有弱连通图,将有向图的所有的有向边替换为无向边,所得到的图称为原图的基图。如果一个有向图的基图是连通图,则有向图是弱连通图。
简单理解就是,一个有向图,如果不是强连通图,把所有的边从无向看成有向,把有向图看成无向图,这时他如果变成了连通图,那么就说他是弱连通图。
上面在讲解有向图和无向图时引出了两个概念,强连通图和连通图,我先从无向图中的连通图来开始介绍
在无向图连通图的问题中,有两个概念非常重要,割点和桥,在大多数问题中都需要求这两个东西,在讲这两个东西之前,先来给大家介绍几个前置概念。
点连通度与边连通度 by kuangbin
在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合。
一个图的点连通度的定义为,最小割点集合中的顶点数。
类似的,如果有一个边集合,删除这个边集合以后,原图变成多个连通块,就称这个点集为割边集合。
一个图的边连通度的定义为,最小割边集合中的边数。
简单总结:
点双连通:删掉一个点之后,图仍联通
边双连通:删掉一条边之后,图仍联通
双连通
定义:在无向连通图中,如果删除该图的任何一个结点或边都不能改变该图的连通性,则该图为双连通的无向图。,和点连通度与边连通度来结合这来说,就是点连通度或边连通度大于1的图。
ps:这部分概念理解知道就好,对于做题来说帮助不是很大。
双连通图割点与桥 by kuangbin
如果一个无向连通图的点连通度大于 1,则称该图是点双连通的 (point biconnected),简称双连通或重连通。一个图有割点,当且仅当这个图的点连通度为 1,则割点集合的唯一元素
被称为割点 (cut point),又叫关节点 (articulation point)。
这里割点的概念就出来了,大概意思就是,如果连通图里有一个点,把这个点和他所连接的边删掉,那么他就不连通了,那么这个点就叫做割点,下图标红的点就是割点。
(图源自网络,侵删)
理解了割点,那么理解割边也就很容易了
如果一个无向连通图的边连通度大于 1,则称该图是边双连通的 (edge biconnected),简称双
连通或重连通。一个图有桥,当且仅当这个图的边连通度为 1,则割边集合的唯一元素被称为桥 (bridge),又叫关节边 (articulation edge)。
下图标红的边就是割边。
图源自网络,侵删)
可以看出,点双连通与边双连通都可以简称为双连通,它们之间是有着某种联系的,下文中提到的双连通,均既可指点双连通,又可指边双连通。
PS:而如果有桥,这个图必有割点,如果有割点,不一定会有桥,这个很好证明,看看图就知道了。
总结:若一个无向图中的去掉任意一个节点(一条边)都不会改变此图的连通性,即不存在割点(桥),则称作点(边)双连通图。
相信看过上面如此详细的概念,大家对割点和割边已经有了深刻的认识了,那么这里就给大家求割点和割边的两道入门题,学习如何用求tarjan算法求割点割边,可以在啊哈算法中看相应章节,个人感觉是市面上能找到资料里写的最好的了。
luogu P3388 【模板】割点(割顶)
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
vector<int> g[20010];
int dfn[20010],low[20010],iscut[20010],son[20010];
int deep,root,n,m,ans;
int tarjan(int u,int fa)
{
int child=0,lowu;
lowu=dfn[u]=++deep;
int sz=g[u].size();
for(int i=0;i<sz;i++)
{
int v=g[u][i];
if(!dfn[v])
{
child++;
int lowv=tarjan(v,u);
lowu=min(lowu,lowv);
if(lowv>dfn[u])
{
iscut[u]=1;
}
}
else
{
if(v!=fa&&dfn[v]<dfn[u])
{
lowu=min(lowu,dfn[v]);
}
}
}
if(fa<0&&child==1)
{
iscut[u]=false;
}
low[u]=lowu;
return lowu;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int from,to;
scanf("%d%d",&from,&to);
g[from].push_back(to);
g[to].push_back(from);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
root=i;
tarjan(i,-1);
}
}
for(int i=1;i<=n;i++){
if(iscut[i]){
ans++;
}
}
printf("%d\n",ans);
for(int i=1;i<=n;i++){
if(iscut[i]){
printf("%d ",i);
}
}
}
luogu P1656 炸铁路(割边)
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
vector<pair<int,int>>bridge;
vector<int> g[10010];
int dfn[10010],low[10010];
int deep,root,n,m,ans;
bool cmp(pair<int,int> a,pair<int,int> b){
if(a.first == b.first)
return a.second < b.second;
else
return a.first < b.first;
}