咳咳咳,之前对tarjan半生不熟的,还转载了百度百科的tarjan,现在想起来真是惭愧啊。
为了弥补在图论方面的弱项,最新学习了tarjan,边学边分享问题和想法吧,这样可能更好理解一些。
首先,有很多专业术语。
1.强连通分量
假设我们有一个子图。
这个子图的任意两点都可以互相到达,则我们称这个子图为一个强连通分量
2.DAG
有向无环图
3.边双联通分量
一个无向图(这很重要)中,若是任意删除一条边,都不改变图的连通性,则称这个图为双联通图,同理,在某一个子图里,若是任意删除一条边,都不改变这个子图的连通性,则称这个子图为一个双联通分量。
4.点双联通分量
同上,把边换成点。
PS:
一个双联通分量中,从一个点到另一个点的路径必定至少有两条,且不经过相同的边(点)
5.割边(桥)
在一个连通图中,若删除某一条边使得原图变成两个联通快,则称这条边为割边,也叫桥。
6.割点
同5,把边换成点。
知道这些就差不多了。
我们知道,图分有向图和无向图,对于两种图,tarjan的用法以及求的东西也不一样。
定义
dfn[i]表示第i个点的dfs序,也就是第几个被访问到的。
low[i]表示第i个点以及它的子树中最小的dfn
d这是一个栈,用来存子树。
有向图
我学的时候,讲师说只用于缩强连通分量,也就是缩点。
但是它还可以求强连通分量的个数,大小(怀疑是不是太显然讲师不屑于讲)
在讲如何做之前,我们知道有向图有三种边(讲师讲的)
1.树边
2.返祖边(返回祖先的边)
3.横插边(横插到另一个子树的边)
在这个图中,a,b,c,d,f是树边
e是横插边
h是返祖边
返租边会用到,横插边不理它。
就着上边的图,手动模拟tarjan怎么算强连通分量的。
首先在1,dfn[1]=low[1]=1,d[1]
然后去到2,dfn[2]=low[2]=2,d[1,2]
去到4,dfn[4]=low[4]=3,d[1,2,4]
没地方去了(完全没地方可以去)
没地方去了就是dfn[i]=low[i]了
它自己就是最小的,是起始点。
这时4就是一个强连通分量
然后把4以及它的子树(如果有)都退栈
怎么退栈?
我们深搜,搜完后父亲一定在儿子前面,
所以只要退到4都退栈就行了,d[1,2]
这时回到2,low[4]>low[2],所以不理
去到5,dfn[5]=low[5]=4,d[1,2,5]
这时有一条边连到3,我们去到3
疑问:按照很多博客的说法,横插边是不理的,为什么这里走了。
思考:这图画错了。
正确的图(并不完整,等会给完整的):
画的有些恶心。。。,但是大致可以看出来。
好我们接着模拟,dfn[3]=low[3]=5,d[1,2,5,3]
去到6,dfn[6]=low[6]=6,d[1,2,5,3,6]
这时有一条边连到1,这时一条返祖边,我们不走,但是更新low值
low[6]>dfn[1],所以将low[6]更新为dfn[1]。
对于返祖边,假设从x到y
low[x]=min(low[x],dfn[y]);
但对于树边,假设从x到y
low[w]=min(low[x],low[y]);
疑问:为什么?
看回定义,是它子树里最小的dfn。
如果是树边,
low[y]记录的是y以及y的子树中最小的,y的子树也就是x的子树,所以,要算low[y]。
如果是返祖边,y是x的祖先,y的子树中的节点也是x的祖先,所以不能算,只能算dfn[y]
继续模拟,没边了
我们惊奇地发现虽然没边了,但是dfn[6]和low[6]不相等,说明这不是强连通分量的起始点。
返回3,更新3的low值,low[3]=low[6]=1(树边)
dfn[3]<>low[3]
返回5,返回2,返回1,low[5]=low[2]=1(手动省略了一点过程)
现在给完整图