最近刚刚学了
Tarjan
算法,于是就写了这篇博客
那么
Tarjan
算法有什么用呢?
对于一个图来说,它的性质其实是很少的,因此我们能对它进行的操作方式也极少
于是将其转化为一颗树(如果是有向图不一定是树)是一个非常重要的思路
而
Tarjan
算法则是用以进行这种转化的比较高效的算法
即用以进行无向图/有向图的联通分量缩点的过程
那么这就需要涉及到一些定义:
无向图:
割点:
若删掉某点
P
后,无向图
桥(割边):
若删掉某条边
B
后,无向图
点双连通图:
没有割点的图称为点双连通图
边双连通图:
没有割边的图称为边双连通图
双联通分量:
无向图 G 的极大【不是最大的意思,而是尽可能大】(点/边)双连通子图称为(点/边)双连通分量。
缩点:
即把一个连通分量缩为一个点的过程,就是删除该连通分量内所有的点和边,
然后新建一个点,向所有与连通分量中的点有边相连的点连边
有向图
在有向图
强连通图:
如果有向图
G
的每两个顶点都强连通,则称
强连通分量:
有向图的极大强连通子图,称为强连通分量
接下来正式介绍
Tarjan
算法的流程
Tarjan
算法对于图中的每个节点引入了两个值:
dfn[x]:
即
dfs
序,储存节点
x
是
low[x]:
储存节点
x
的子树不经过搜索树所能到达的
而求取这两个值得过程即为:
void dfs(int x,int pre){
dfn[x]=++tp;low[x]=tp;
for(int i=head[x];~i;i=edge[i].nxt){
int y=edge[i].to;
if(y==pre)continue;
if(dfn[y])low[x]=min(low[x],dfn[y]);//若节点y已被遍历过,那么用节点y的dfs序来更新x的low值
else {
dfs(y,x);
low[x]=min(low[x],low[y]);//若节点y没有被遍历过,那么用节点y的low值来更新节点x的low值
/*
这一部分即为
*/
}
}
}
//自己领悟吧,有图
//每个小圆为节点,圆上编号代表dfn值,红色数字代表low值,深色为树边,其余为非树边
“`
那么这两个值有什么用呢?
有以下几种用法:
求割点:
根据割点的定义,我们知道当我们删除节点
x
后整个图若分为两个或以上的部分则该点为割点
那么该怎么判断这个点是不是割点呢?
我们知道,如果节点
那么一旦删除节点
x
,该子树就会从原来的图上脱离,即节点
那么就好办了,即当节点
x
的任一子节点
当然,这是
当
x
为根节点时,需要有两个子节点的
求桥:
求桥类似于求割点
而判桥时不需要分别根节点和非根节点
对于任一节点
则连接他们的这条边是一座桥
求边双联通分量:
求边双联通分量比较简单,找到桥后把所有的桥全部删掉,
这时候整张图就变成了一个个孤立的边双联通分量
显然,每个节点只会出现在一个边双联通分量中
求点双联通分量:
求点双联通分量时我们需要用到一个栈
每次
dfs
到某一个点时我们将这个点
push
到栈中
而当我们寻找到一个割点时,就代表在栈中它以后(包括它)的这些点组成了一个点双联通分量
(这个自行脑补,不好描述,但还是比较容易证明其正确性的)
所以当我们发现
low[p]>=dfn[x]
时
就将
p
以及
因此对于非割点来说,只会在一个点双联通分量中出现
而对于割点来说,会出现在多个点双联通分量中
这就是点双联通的题一般难于边双联通的原因
(
求强联通分量:
这种题我还没做过,毕竟有向图的题本来就比较少。。
求强联通分量时,由于从每个点出发不一定能遍历整个图
于是我们需要从每一个未访问的点开始
dfs
,而后将
dfs
过程中访问到的点加入栈中
如果发现对于任一节点
x
,满足
在栈中将
x
以及
因为点与点之间的强连通具有传递性(若
1<−>2
,
2<−>3
,则
1<−>3
)
而
low[x]
表示与
x
强连通的
所以当
low[x]==dfn[x]
时说明节点
x
之前的节点中已经没有与其属于同一个强连通分量的点了。
显而易见的是,每个节点只会出现在一个强联通分量中
以上就是