tarjan算法其实是在dfs基础上维护一个标志值来判断当前点是否是某种特殊点,而学习tarjan算法,通过tarjan的DEPTH-FIRST SEARCH AND LINEAR GRAPH ALGORITHMS学习好像并不是什么好的选择…但是读一读dalao的论文还是挺有趣的,本文对tarjan的论文进行简单翻译和补充我自己的理解。
一些符号和基础定义:
对于图
G
=
(
V
,
E
)
G=(V,E)
G=(V,E)来说。
路径
p
:
v
⇒
∗
w
p:v\stackrel{*}{\Rightarrow}w
p:v⇒∗w表示存在一个从
v
v
v到
w
w
w的点和边的序列。
如果一条路径上所有的点都互不相同,则我们称其为简单路径(simple path)。
路径
p
:
v
⇒
∗
v
p:v\stackrel{*}{\Rightarrow}v
p:v⇒∗v是一个闭合回路(closed path)。
一个闭合回路
p
:
v
⇒
∗
v
p:v\stackrel{*}{\Rightarrow}v
p:v⇒∗v如果它的所有边各不相同,并且在
p
p
p中只有
v
v
v出现了两次,那么我们称p为一个环(cycle)。
如果两个环它们的循环排列互相相同,那么我们把两个环视为同一个环。
如果无向图中所有的点对之间都有一条路径,那么它是连通的(connected)。
对于一棵(有向根)树
T
T
T来说。
无向版本的
T
T
T是连通的,并且有一个根节点它不是任何边的终点(head),而除了根节点外的所有点都是且只是某一条边的终点。
v
→
w
v\rightarrow w
v→w表示"
(
v
,
w
)
(v,w)
(v,w)是
T
T
T中的一条边"。
v
→
∗
w
v\stackrel{*}{\rightarrow}w
v→∗w表示"在
T
T
T中存在一条从
v
v
v到
w
w
w的路径"。
如果
v
→
w
v\rightarrow w
v→w,那么
v
v
v是
w
w
w的父节点(father),
w
w
w是
v
v
v的子节点(son)。
如果
v
→
∗
w
v\stackrel{*}{\rightarrow}w
v→∗w,那么
v
v
v是
w
w
w的祖先节点(ancestor),
w
w
w是
v
v
v的后代节点(descendant)。
每个点都是自己的祖先节点和后代节点。
如果
v
v
v是
T
T
T中的一个点,那么
T
v
T_v
Tv是由
v
v
v的所有后代节点组成的"
T
T
T的子树"。
如果
G
G
G是一个有向图,如果树
T
T
T包含
G
G
G中所有的点并且
T
T
T是
G
G
G的子图,那么
T
T
T是
G
G
G的一个生成树。
定义1: G = ( V , E ) G=(V,E) G=(V,E)是一个图,对于每个点 v ∈ V v\in V v∈V我们可以构造一个包含所有的有 ( v , w ) ∈ E (v,w)\in E (v,w)∈E的点 w w w组成的列表,我们称这样的列表为 v v v的邻接表; 对于图 G G G的所有点的邻接表构成的集合,我们称之为图 G G G的邻接结构。
这就是我们熟悉的邻接表,没什么好说的。
定义2: 设
P
P
P是一个有向图,它的边集由两个不相交的集合组成,分别用
v
→
w
v\rightarrow w
v→w和
v
⇢
w
v\dashrightarrow w
v⇢w表示其中的边,假定P满足以下性质:
(i) 包含
v
→
w
v\rightarrow w
v→w的边构成的子图
T
T
T是
P
P
P的一棵生成树。
(ii)
⇢
⊆
(
→
∗
)
−
1
\dashrightarrow \subseteq (\stackrel{*}{\rightarrow}) ^{-1}
⇢⊆(→∗)−1,简单来说如果有
u
⇢
v
u\dashrightarrow v
u⇢v,那么一定有
v
→
∗
u
v \stackrel{*}{\rightarrow} u
v→∗u,对于所有P中没有在T中的边,都把某个点连接到了它在T中某个祖先节点上。
那么
P
P
P就是一个palm树,我们把所有
v
⇢
w
v\dashrightarrow w
v⇢w这样的边称为
P
P
P的
f
r
o
n
d
frond
frond。
我们可以看到palm树只有祖先和后代之间可能有边,借助这一性质可以帮助我们完成很多事情。
定理1: P P P是无向连通图 G G G通过dfs生成的有向图,那么 P P P是一棵palm树; 相反地,任何palm树 P P P都可以由无向版本的 P P P通过dfs得来。
我们定义palm树这样的结构就是为了和dfs建立联系,下面我们给出一种构造的算法。
算法1:
BEGIN
INTEGER i;
PROCEDURE DFS(v, u); COMMENT vertex u is the father of
vertex v in the spanning tree being constructed;
BEGIN
NUMBER(v) := i := i+1;
FOR w in the adjacency list of v DO
BEGIN
IF w is not yet numbered THEN
BEGIN
construct arc $v->w$ in P;
DFS(w, v);
END
ELSE IF NUMBER(w) < NUMBER(v) and w != u
THEN construct arc $v-->w$ in P;
END;
END;
i := 0;
DFS(s, 0);
END;
需要注意
N
U
M
B
E
R
(
w
)
<
N
U
M
B
E
R
(
v
)
NUMBER(w)<NUMBER(v)
NUMBER(w)<NUMBER(v)是必要的条件,避免祖先再沿着
f
r
o
n
d
frond
frond的反向指向后代。关于这个构造的正确性,因为它本身是不反直觉的,所以我只说下为什么这样构造出来的
f
r
o
n
d
frond
frond都是指向祖先的。如果
(
v
,
w
)
(v,w)
(v,w)中
w
w
w已经被number了,那么它有两种情况,一种是
N
U
M
B
E
R
(
v
)
<
N
U
M
B
E
R
(
w
)
NUMBER(v)<NUMBER(w)
NUMBER(v)<NUMBER(w),一种是
N
U
M
B
E
R
(
v
)
>
N
U
M
B
E
R
(
w
)
NUMBER(v)>NUMBER(w)
NUMBER(v)>NUMBER(w)。
我们先看
N
U
M
B
E
R
(
v
)
>
N
U
M
B
E
R
(
w
)
NUMBER(v)>NUMBER(w)
NUMBER(v)>NUMBER(w),如果
w
w
w不是
v
v
v的父亲的话,我们有
(
v
,
w
)
(v,w)
(v,w)一定早于
(
w
,
v
)
(w,v)
(w,v)被检查(不然
w
w
w就是
v
v
v的父亲),但是
w
w
w比
v
v
v先被number,所以唯一的可能是
w
w
w还没有从
D
F
S
(
w
,
−
)
DFS(w,-)
DFS(w,−)中返回,那么显然
v
v
v是
w
w
w的后代,这就是我们添加进的
f
r
o
n
d
frond
frond。
我们再看另一种情况,这里的
w
w
w比
v
v
v晚number,但是我们还在
D
F
S
(
v
,
−
)
DFS(v,-)
DFS(v,−)中,所以显然
w
w
w是
v
v
v已经返回的后代,这种情况下我们在
D
F
S
(
w
,
−
)
DFS(w,-)
DFS(w,−)中已经有了
f
r
o
n
d
frond
frond
(
w
,
v
)
(w,v)
(w,v),所以这种情况我们会进行丢弃。
推论2: 任何无向图 G G G都可以通过把边以适当的方式有向化转换为一个palm树。
其实就是定理1的另一种说法。
定义3: G = ( V , E ) G=(V,E) G=(V,E)是一个无向图。对于 V V V中任意互不相同三点 v v v, w w w, a a a,存在一条路径 p : v ⇒ ∗ w p:v\stackrel{*}{\Rightarrow}w p:v⇒∗w不经过 a a a,那么 G G G是点双连通的。另一方面如果 V V V中存在互不相同的三点 v v v, w w w, a a a,有 a a a总是在任何 p : v ⇒ ∗ w p:v\stackrel{*}{\Rightarrow}w p:v⇒∗w的路径上,且最少存在一条路径,那么 a a a就是图 G G G的割点。
引理3: 无向图
G
=
(
V
,
E
)
G=(V,E)
G=(V,E),我们定义一种在边集中的等价关系: 当且仅当两边属于同一个环时,两边是等价的。我们让不同的等价类分别为
E
i
E_i
Ei,
1
≦
i
≦
n
1\leqq i\leqq n
1≦i≦n,并且
G
i
=
(
V
i
,
E
i
)
G_i=(V_i, E_i)
Gi=(Vi,Ei),
V
i
V_i
Vi是
E
i
E_i
Ei中的点的集合; KaTeX parse error: Expected 'EOF', got '}' at position 35: …((v, w)\in E_i)}̲,那么:
(i) 对于每个
1
≦
i
≦
n
1\leqq i\leqq n
1≦i≦n,
G
i
G_i
Gi是点双连通的。
(ii) 没有
G
i
G_i
Gi是某个点双连通图
G
G
G的真子图。
(iii) 图
G
G
G的每个割点在所有
V
i
(
1
≦
i
≦
n
)
V_i (1\leqq i\leqq n)
Vi(1≦i≦n)中一共至少出现两次。图
G
G
G的每个非割点在所有
V
i
(
1
≦
i
≦
n
)
V_i (1\leqq i\leqq n)
Vi(1≦i≦n)中一共出现且只出现一次。
(iv) 对于任意的
1
≦
i
,
j
≦
n
1\leqq i,j\leqq n
1≦i,j≦n,
V
i
∩
V
j
V_i\cap V_j
Vi∩Vj至多只包含一个点,且这个点是图的割点。
那么图
G
G
G的子图
G
i
G_i
Gi被叫做
G
G
G的点双连通分量。
我们给出了割点的定义和点双连通分量的定义,为了便于寻找点双连通分量,我们再给出 L O W P T LOWPT LOWPT的定义。
L O W P T ( v ) LOWPT(v) LOWPT(v)是集合 { v } ∪ { w ∣ v → ∗ ⇢ w } \{v\}\cup\{w|v\stackrel{*}{\rightarrow}\dashrightarrow w\} {v}∪{w∣v→∗⇢w}中最小的点。即 L O W P T ( v ) LOWPT(v) LOWPT(v)是最小的从v经过0或更多条树边再加1条 f r o n d frond frond可达的最小的点。
引理4: 对于无向图 G G G, P P P是有向化 G G G中的边形成的palm树, T T T是 P P P的生成树,假定 p : v ⇒ ∗ w p:v\stackrel{*}{\Rightarrow}w p:v⇒∗w是在 G G G中任意一条这样的路径,那么 p p p中包含一个点,且这个点是 v v v和 w w w在 T T T中的祖先节点。
设以 u u u为根的 T u T_u Tu是 T T T中包含 p : v ⇒ ∗ w p:v\stackrel{*}{\Rightarrow}w p:v⇒∗w中所有节点最小的子树,如果 u = v u=v u=v或是 u = w u=w u=w,那么显然。否则我们假设 T u 1 T_{u_{1}} Tu1和 T u 2 T_{u_{2}} Tu2是两棵包含了 p p p上点的子树,并且 u → u 1 u\rightarrow u_1 u→u1, u → u 2 u\rightarrow u_2 u→u2(即 u 1 u_1 u1和 u 2 u_2 u2是 u u u的儿子)。如果只有一棵这样的子树,那么由于 T u T_u Tu是最小的会使得 u u u是路径上的点(反证);如果两棵这样的子树存在,那么路径 p p p如果想要从 T u 1 T_{u_{1}} Tu1到 T u 2 T_{u_{2}} Tu2就必须要经过u,因为无论是 → \rightarrow →还是 ⇢ \dashrightarrow ⇢都与祖先节点相连,而两棵子树中任何点都不可能是另一棵子树中的点的祖先。而 u u u是两者所有节点的祖先节点,并且 u u u是 T u T_u Tu中唯一满足这个条件的点,所以 u u u一定会在 p p p上。
引理5: 让 G G G是一个无向连通图, P P P是有向化 G G G中的边后形成的palm树, T T T是 P P P的生成树。假定 a a a, v v v, w w w是图 G G G中三个不同的点,并且有 ( a , v ) ∈ T (a,v)\in T (a,v)∈T, w w w不是 v v v在 T T T中的后代节点(即在 T T T中不存在 ( v → ∗ w ) (v\stackrel{*}{\rightarrow}w) (v→∗w))。如果 L O W P T ( v ) ≧ a LOWPT(v)\geqq a LOWPT(v)≧a,那么 a a a是 G G G的一个割点,并且移除a将导致 v v v和 w w w断连。相反地,如果 a a a是 G G G的一个割点,那么存在点 v v v和 w w w满足上述的性质。
如果有
a
→
v
a\rightarrow v
a→v,并且
L
O
W
P
T
(
v
)
≧
a
LOWPT(v)\geqq a
LOWPT(v)≧a,那么任何与
v
v
v相关的路径(无论是
v
v
v出发,还是以
v
v
v为终点)如果不经过
a
a
a的话,那么只能在子树
T
v
T_v
Tv中,但是这棵子树是没有点
w
w
w的,第一部分得证。(其实这里一开始我产生了疑问,有向化的palm树没有路径并不能代表
G
G
G没有路径,但是这里把这里的路径想成是无向版本palm树的路径即可)
为了证明反命题,我们设
a
a
a是图
G
G
G的一个割点。如果
a
a
a是
P
P
P的根,那么就最少有两条树边从
a
a
a发出(不然显然去除
a
a
a不会影响连通性,即与
a
a
a是割点矛盾),我们设有两条边分别为
(
a
,
v
)
(a,v)
(a,v)和
(
a
,
w
)
(a,w)
(a,w),那么显然
L
O
W
P
T
(
v
)
≧
a
LOWPT(v)\geqq a
LOWPT(v)≧a,并且
w
w
w也不是
v
v
v的后代;如果
a
a
a不是
P
P
P的根,那么考虑删除
a
a
a后形成图
G
G
G的连通分量,其中某一个连通分量一定只包含
a
a
a的后代(如果没有这样的连通分量,那么由于除去
T
a
T_a
Ta即
a
a
a的后代节点外的点的连通性都不受影响,图不会被分割成至少两个连通分量,这与
a
a
a是割点矛盾)(并且这个连通分量只会包含一个
a
a
a的子节点,由引理4,准确来说是类似引理4的证明),我们设
v
v
v是这个连通分量中
a
a
a的子节点,
w
w
w是
v
v
v的某个真祖先节点,那么我们有
a
→
v
a\rightarrow v
a→v,
L
O
W
P
T
(
v
)
≧
a
LOWPT(v)\geqq a
LOWPT(v)≧a(否则
v
v
v就会与
a
a
a的真祖先节点之间可达),
w
w
w不是
v
v
v的后代节点。因此反命题得证。
这里的证明很重要,它使得"
a
a
a有子节点
v
(
L
O
W
P
T
(
v
)
≧
a
)
v(LOWPT(v)\geqq a)
v(LOWPT(v)≧a)且存在一个不是
v
v
v的后代节点的点"成为了"
a
a
a是割点"的充要条件,让我们很方便地判断一个点是否是割点。
推论6: G G G是连通无向图, P P P是有向化 G G G的边后形成的palm树, T T T是 P P P的生成树。如果 C C C是 G G G中一个点双连通分量,那么 C C C中的点组成了 T T T中的一棵子树,并且这棵子树的根要么是 G G G的割点,要么是 T T T的根。
这条推论把找点双连通分量的问题转化为了找割点的问题。这里我们给出一个更方便计算的
L
O
W
P
T
LOWPT
LOWPT的式子,进而给出求点双连通分量的算法。
L
O
W
P
T
(
v
)
=
m
i
n
(
{
N
U
M
B
E
R
(
v
)
}
∪
{
L
O
W
P
T
(
w
)
∣
v
→
w
}
∪
{
N
U
M
B
E
R
(
w
)
∣
v
⇢
w
}
)
LOWPT(v) = min(\{NUMBER(v)\}\cup\{LOWPT(w)|v\rightarrow w\}\cup\{NUMBER(w)|v\dashrightarrow w\})
LOWPT(v)=min({NUMBER(v)}∪{LOWPT(w)∣v→w}∪{NUMBER(w)∣v⇢w})
算法2:
BEGIN
INTEGER i;
procedure BICONNECT(v, u);
BEGIN
NUMBER(v) := i := i+1;
LOWPT(v) := NUMBER(v);
FOR w in the adjacency list of v DO
BEGIN
IF w is not yet numbered THEN
BEGIN
add (v, w) to stack of edges;
BICONNECT(w, v)
LOWPT(v) := min(LOWPT(v), LOWPT(w));
IF LOWPT(w) >= NUMBER(v) THEN
BEGIN
start new biconnected component;
WHILE top edge e = (u1, u2) on edge
stack has NUMBER(u1)
>= NUMBER(w) DO
delete(u1, u2) from edge stack and
add it to current component;
delete (v, w) from edge stack and add
it to current component;
END;
END
ELSE IF (NUMBER(w) < NUMBER(v)) and (w != u) THEN
BEGIN
add (v, w) to edge stack;
LOWPT(v) := min(LOWPT(v), NUMBER(w));
END;
END;
END;
i:=0;
empty the edge stack;
FOR w a vertex DO IF w is not yet numbered THEN BICONNECT(w, 0);
END;
定理7: 该点双连通算法对于一个有 V V V个点 E E E条边的图需要 O ( V , E ) O(V,E) O(V,E)的空间和时间。
这里的过程其实和前面的dfs别无二致,而这里的额外操作与 E E E成比例,palm树上每条边都最多会入栈一次,所以显然。
定理8: 该点双连通分量算法对于任何无向图都可以正确给出所有的点双连通分量。
因为dfs的过程其实是对每个连通分量进行分析的,所以我们只需要证明连通图中的正确性即可。
我们用归纳证明。
当连通图
G
G
G中没有边时,即
G
G
G是空图或是只有1个点时。这个算法会正确地终止,并且列出没有点双连通分量,显然这种情况下它是正确的。
假定算法在连通图的边数小于等于
E
−
1
E-1
E−1时会得到正确结果,我们把算法应用到一个有
E
E
E条边的连通图
G
G
G上。
我们假设我们应用算法时会跑出来至少两个点双连通分量,我们设第一个点双连通分量为
G
′
G'
G′,显然当
G
′
G'
G′的边数小于
E
E
E,那么我们可以先只用
G
′
G'
G′中的点和边,从其中
N
U
M
B
E
R
NUMBER
NUMBER最小的点即
G
′
G'
G′的根节点出发,然后再把
G
G
G中所有
G
′
G'
G′中除了
G
′
G'
G′的根节点以外的点及对应的边都删去,再从原本的根节点出发,这样我们会有和直接从根节点出发完全相同的结果,并且由边数小于
E
E
E情况下结果是正确的保证正确性。(其实这里有着问题,对两个子图正确,不代表对子图合起来的图正确,但是我们知道
G
′
G'
G′的根节点是割点,那么一定有一个只包含它后代节点的点双连通分量,而
G
′
G'
G′就是这样的一个分量,补上这样的描述应该就够了)
我们接下来证明只有一个连通分量的情况下它是正确的,首先除了根节点以外的节点都不是割点这点已经由引理5保证了。我们用反证法,如果根节点是割点,那么它一定至少有两个子节点(不然不能满足引理5),但是我们知道算法运行时在检查根节点和子节点相连的那条边时,一定会生成一个新的连通分量。这与我们只有一个连通分量矛盾,故根节点也不是割点,即
G
G
G是一个点双连通图。
综上得证。
tarjan这里的证明说实话巧妙得有点让人有点难以接受,但是它确实应该是正确的。
定义4: 有向图 G G G,如果对于 G G G中任何点对 v , w v,w v,w都存在路径 p 1 : v ⇒ ∗ w p_1:v\stackrel{*}{\Rightarrow}w p1:v⇒∗w和 p 2 : w ⇒ ∗ v p_2:w\stackrel{*}{\Rightarrow}v p2:w⇒∗v,那么我们称图 G G G是强连通的。
就是最普通的强连通图(strongly connected graph)定义。
引理9: 有向图
G
=
(
V
,
E
)
G=(V,E)
G=(V,E),我们定义点集上的一种等价关系: 对于点
v
v
v和点
w
w
w,若存在一条包含有
w
w
w的路径
p
:
v
⇒
∗
v
p:v\stackrel{*}{\Rightarrow}v
p:v⇒∗v,那么我们称
v
v
v和
w
w
w是等价的。我们可以
V
V
V中的不同的等价类为
V
i
(
1
≦
i
≦
n
)
V_i(1\leqq i\leqq n)
Vi(1≦i≦n)。
G
i
=
(
V
i
,
E
i
)
G_i = (V_i,E_i)
Gi=(Vi,Ei),
E
i
=
{
(
v
,
w
)
∈
E
∣
v
,
w
∈
V
i
}
E_i =\{(v,w)\in E|v,w\in V_i\}
Ei={(v,w)∈E∣v,w∈Vi}。那么:
(i) 每个
G
i
G_i
Gi都是强连通的。
(ii) 没有
G
i
G_i
Gi是某个强连通图的真子图。
那么我们称这些
G
i
G_i
Gi为
G
G
G的强连通分量。
也是普通的强连通分量(strongly connected component)定义。
我们会注意到在有向图中,我们的得力助手palm树不见了…但是也并不是什么限制都没有的。在有向图中,除了树边、 f r o n d frond frond外,又新出现了一种 c r o s s − l i n k cross-link cross−link,即从某棵子树中的点指向另一棵子树中的某个点的边。除了上述这些边以外的边,如重边或是指向非子节点的后代节点的边,不会改变两点之间有没有路径的事实,所以我们丢弃。
我们很容易会发现任何 c r o s s − l i n k cross-link cross−link ( v , w ) (v,w) (v,w)都具有着 N U M B E R ( v ) > N U M B E R ( w ) NUMBER(v)>NUMBER(w) NUMBER(v)>NUMBER(w)的性质(类似前面,我们现在在 D F S ( v , − ) DFS(v,-) DFS(v,−)中,所以如果 N U M B E R ( v ) < N U M B E R ( w ) NUMBER(v)<NUMBER(w) NUMBER(v)<NUMBER(w),那么 w w w一定是在 D F S ( v , − ) DFS(v,-) DFS(v,−)中被 N U M B E R NUMBER NUMBER的,即 w w w是 v v v的某个已经返回的后代节点,这是我们前述说的丢弃的边)。我们还可以有进一步的结论,在一串树边后的 c r o s s − l i n k cross-link cross−link指向某个"不在以起点为根的子树上"的点的 N U M B E R NUMBER NUMBER一定小于起点的 N U M B E R NUMBER NUMBER,方法是类似的,因为检查这条 c r o s s − l i n k cross-link cross−link时我们起点的 D F S DFS DFS还没有返回,所以此时任何比它大的 N U M B E R NUMBER NUMBER都一定是后代节点,故而矛盾。
引理10: 图 G G G中的点 v v v和点 w w w在同一个强连通分量中, F F F是 G G G通过dfs得到的生成树森林。那么 v v v和 w w w在 F F F中有一个公共的祖先。更进一步的,如果 u u u是 v v v和 w w w的标号最大的祖先(即lca),那么 u u u和 v v v以及 w w w在同一个强连通分量中。
不妨令
v
<
w
v< w
v<w,
p
p
p是
v
v
v到
w
w
w的路径,假设
T
x
T_x
Tx是在
F
F
F中最小的包含
p
p
p中所有点的根为
x
x
x的子树。
T
x
T_x
Tx是一定存在的,因为
p
p
p只可能从一棵子树前往一棵有着更小
N
U
M
B
E
R
NUMBER
NUMBER的子树,而不可能前往一棵有着更大
N
U
M
B
E
R
NUMBER
NUMBER的子树(这几个子树互不包含)。如果
p
p
p包含
F
F
F中两棵或更多的树的点,那么它不可能到达
w
w
w,因为
v
<
w
v< w
v<w。
因此
T
x
T_x
Tx一定存在,进而
v
v
v和
w
w
w在
F
F
F中有着公共的祖先节点。类似引理4中的证明,如果
x
=
v
x=v
x=v或
x
=
w
x=w
x=w那么显然。否则我们假设
T
x
1
T_{x_{1}}
Tx1和
T
x
2
T_{x_{2}}
Tx2是两棵包含了
p
p
p上点的子树,并且
x
→
x
1
x\rightarrow x_1
x→x1,
x
→
x
2
x\rightarrow x_2
x→x2(即
x
1
x_1
x1和
x
2
x_2
x2是
x
x
x的儿子)。如果只有一棵这样的子树,那么由于
T
x
T_x
Tx是最小的,会有
x
x
x一定在
p
p
p上(反证);如果两棵这样的子树存在,因为
v
<
w
v< w
v<w,所以我们不妨假设
x
1
<
x
2
x_1<x_2
x1<x2,且
p
p
p第一次离开
T
x
1
T_{x_1}
Tx1后第一次进入的其他子树是
T
x
2
T_{x_2}
Tx2,那么路径
p
p
p如果想要离开
T
u
1
T_{u_{1}}
Tu1只能通过树边或是
f
r
o
n
d
frond
frond,因为
c
r
o
s
s
−
l
i
n
k
cross-link
cross−link只能指向更小的
N
U
M
B
E
R
NUMBER
NUMBER,它不能直接通过
c
r
o
s
s
−
l
i
n
k
cross-link
cross−link进入
T
x
2
T_{x_2}
Tx2,那么
p
p
p上必然有一条指向
x
x
x的边。综上
v
v
v和
w
w
w的某个公共祖先节点
x
x
x一定在
v
v
v到
w
w
w的路径
p
p
p上,那么由于
w
w
w到
v
v
v还存在一条路径,那么
x
x
x、
w
w
w、
v
v
v一定在同一个
S
C
C
SCC
SCC中。
设
u
u
u是
v
v
v和
w
w
w的lca,那么我们有
u
u
u一定在
x
→
∗
v
x\stackrel{*}{\rightarrow} v
x→∗v和
x
→
∗
w
x\stackrel{*}{\rightarrow} w
x→∗w上,进而推出
u
u
u一定和
x
x
x、
v
v
v、
w
w
w在同一个
S
C
C
SCC
SCC中。
推论11: C C C是 G G G中的一个强连通分量,那么 C C C中的点构成了 G G G的生成树森林 F F F中的某棵树的子树,这棵子树的根我们称之为强连通分量 C C C的根。
这个推论和推论6一样,把找
S
C
C
SCC
SCC转化为了找点是否是
S
C
C
SCC
SCC的根。根其实就是dfs过程中某个
S
C
C
SCC
SCC第一个被
N
U
M
B
E
R
NUMBER
NUMBER的点,依次拿去不含其它根的子树,每次拿去的部分就是一个
S
C
C
SCC
SCC。
说实话
L
O
W
P
T
LOWPT
LOWPT的给出已经挺突兀的了,下面的
L
O
W
L
I
N
K
LOWLINK
LOWLINK的定义更是突兀,但是这引出了判断一个点是否是
S
C
C
SCC
SCC根的方法。
L O W L I N K ( v ) = m i n ( { v } ∪ { w ∣ v → ∗ ⇢ w & ∃ u ( u → ∗ v & u → ∗ w & u 和 w 在 G 的 同 一 个 强 连 通 分 量 中 ) } ) LOWLINK(v)=min(\{v\}\cup\{w|v\stackrel{*}{\rightarrow}\dashrightarrow w\&\exists u(u\stackrel{*}{\rightarrow}v \& u\stackrel{*}{\rightarrow}w \& u和w在G的同一个强连通分量中)\}) LOWLINK(v)=min({v}∪{w∣v→∗⇢w&∃u(u→∗v&u→∗w&u和w在G的同一个强连通分量中)})
L O W L I N K ( v ) LOWLINK(v) LOWLINK(v)就是与 v v v在同一 S C C SCC SCC的经0或更多条树边和至多一条 f r o n d frond frond或 c r o s s − l i n k cross-link cross−link可达的最小的点。说起来其实非常非常拗口,但是就是这样。
引理12: 我们计算出了有向图 G G G中基于 G G G的某个通过dfs得到生成树森林 F F F的每个点的 L O W L I N K LOWLINK LOWLINK,则对于任一点 v v v当且仅当 L O W L I N K ( v ) = v LOWLINK(v)=v LOWLINK(v)=v时,是 G G G的某个强连通分量的根。
首先,如果
v
v
v是根的话,那么
L
O
W
L
I
N
K
(
v
)
=
v
LOWLINK(v)=v
LOWLINK(v)=v是显然的,因为如果
L
O
W
L
I
N
K
(
v
)
<
v
LOWLINK(v)<v
LOWLINK(v)<v的话,那么就说明
v
v
v有一个真祖先节点才是根,这与
v
v
v是根矛盾。
然后我们考虑另一方面,我们假定
u
u
u是这个
S
C
C
SCC
SCC的根,那么由于
u
u
u和
v
v
v在同一个
S
C
C
SCC
SCC中,所以我们有
p
:
v
⇒
∗
u
p:v\stackrel{*}{\Rightarrow}u
p:v⇒∗u,并且
v
v
v在
T
u
T_u
Tu上,考虑
p
p
p上第一条不指向
T
v
T_v
Tv上的点的边,我们设它指向的是
w
w
w。这条边要么是
f
r
o
n
d
frond
frond,要么是
c
r
o
s
s
−
l
i
n
k
cross-link
cross−link,但是无论它是什么我们都会有
w
<
v
w<v
w<v,进而有
L
O
W
L
I
N
K
(
v
)
≦
w
<
v
LOWLINK(v)\leqq w<v
LOWLINK(v)≦w<v,这与
L
O
W
L
I
N
K
(
v
)
=
v
LOWLINK(v)=v
LOWLINK(v)=v矛盾。
综上,
L
O
W
L
I
N
K
(
v
)
=
v
LOWLINK(v)=v
LOWLINK(v)=v和
v
v
v是某个
S
C
C
SCC
SCC的根是等价的。
算法3:
BEGIN
INTEGER i;
PROCEDURE STRONGCONNECT(v);
BEGIN
LOWLINK(v) := NUMBER(v) := i := i+1;
put v on stack of points;
FOR w in the adjacency list of v DO
BEGIN
IF w is not yet numbered THEN
BEGIN COMMENT (v, w) is a tree arc;
STRONGCONNECT(w);
LOWLINK(v) := min(LOWLINK(v), LOWLINK(w));
END
ELSE IF NUMBER(w) < NUMBER(v) DO
BEGIN COMMENT (v, w) is a frond or cross-link;
if w is on stack of points THEN
LOWLINK(v) := min(LOWLINK(v), NUMBER(w));
END;
END;
END;
IF (LOWLINK(v) = NUMBER(v)) THEN
BEGIN COMMENT v is the root of a component;
start new strongly connected component;
WHILE w on top of point stack satisfies
NUMBER(w) >= NUMBER(v) DO
delete w from point stack and put w in
current component
END;
END;
i := 0;
empty stack of points;
FOR w a vertex IF w is not yet numbered THEN STRONGCONNECT(w);
END;
定理13: 该寻找强连通分量的算法需要 O ( V , E ) O(V,E) O(V,E)的空间和时间。
该算法只是建立在dfs基础上做了一点额外操作。额外操作中每个点最多入栈出栈一次,检查是否在栈中可以用boolean数组来实现,因此会有与 V V V和 E E E有线性的关系的时间和空间。
定理14: 该寻找强连通分量的算法正确工作在任何有向图 G G G中。
这里是我困惑的地方,也是我看着篇论文的最重要原因,但是我没有完全读明白。反复读后,我还是坚持觉得tarjan除了前面画错图外这里的证明也有点问题。所以我准备按照自己的理解来写,各位读者有兴趣的话可以去阅读tarjan的论文,引理14在13页的最下方。
因为判断
u
u
u是否是某个
S
C
C
SCC
SCC的根节点时,在
T
u
T_u
Tu上除了
u
u
u以外的点已经全部返回了,所以如果有根的话,以它为根的子树已经全部出栈了。所以如果这个点是根节点的话,我们只管把栈中所有在根之后入栈的点和根出栈放入同一个
S
C
C
SCC
SCC中即可。
那么判断我们的算法是否会正确运行,只需要知道判断根节点是否正确即可,即
L
O
W
L
I
N
K
LOWLINK
LOWLINK的计算是否正确。
我们还是用归纳法,用比较简单的说法来说,就是假设所有在点
v
v
v结束
S
T
R
O
N
G
C
O
N
N
E
C
T
STRONGCONNECT
STRONGCONNECT调用前已经结束调用的点的
L
O
W
L
I
N
K
LOWLINK
LOWLINK的计算都是正确的;用更直观的说法来说,就是所有
{
k
∣
k
<
v
&
k
不
是
v
的
祖
先
节
点
}
∪
{
k
∣
v
→
∗
k
}
\{k|k<v\& k不是v的祖先节点\}\cup\{k|v\stackrel{*}{\rightarrow} k\}
{k∣k<v&k不是v的祖先节点}∪{k∣v→∗k}中的点的
L
O
W
L
I
N
K
LOWLINK
LOWLINK都正确计算。(tarjan的说法中没有除去祖先节点,但是事实上,祖先节点的
L
O
W
L
I
N
K
LOWLINK
LOWLINK还在计算中,它会正确计算,但是我们此时不知道它是否会正确计算,也不需要它正确计算)
首先我们看第一个返回的点,对于它来说最多只可能有
f
r
o
n
d
frond
frond来更新
L
O
W
L
I
N
K
LOWLINK
LOWLINK。如果它没有
f
r
o
n
d
frond
frond显然
L
O
W
L
I
N
K
LOWLINK
LOWLINK的值正确,否则设
∀
f
r
o
n
d
\forall frond
∀frond
v
⇢
w
v\dashrightarrow w
v⇢w,显然
∃
u
(
u
=
w
)
\exists u(u=w)
∃u(u=w)满足我们对
L
O
W
L
I
N
K
LOWLINK
LOWLINK更新的条件。
我们再看其他的点,假设在它前面返回的点都正确计算了
L
O
W
L
I
N
K
LOWLINK
LOWLINK,即所有
{
k
∣
k
<
v
&
k
不
是
v
的
祖
先
节
点
}
∪
{
k
∣
v
→
k
}
\{k|k<v\& k不是v的祖先节点\}\cup\{k|v\rightarrow k\}
{k∣k<v&k不是v的祖先节点}∪{k∣v→k}中的点都正确计算了
L
O
W
L
I
N
K
LOWLINK
LOWLINK。
我们只需要关心
v
→
∗
w
1
⇢
w
2
v\stackrel{*}{\rightarrow} w_1\dashrightarrow w_2
v→∗w1⇢w2,且
w
2
<
v
w_2<v
w2<v这样的路径,因为只有这样的路径才有可能更新
L
O
W
L
I
N
K
(
v
)
LOWLINK(v)
LOWLINK(v)。(需要注意有可能存在
w
1
=
v
w_1=v
w1=v)
假设
u
u
u是
v
v
v和
w
2
w_2
w2的lca,那么有两种情况:
如果
u
u
u和
w
2
w_2
w2不在同一个
S
C
C
SCC
SCC中,那么在
u
→
∗
w
2
u\stackrel{*}{\rightarrow}w_2
u→∗w2上一定存在一个
S
C
C
SCC
SCC的根并且那个点不是
u
u
u,由于那个点会在
v
v
v之前返回,所以它的
L
O
W
L
I
N
K
LOWLINK
LOWLINK一定会正确计算,那么
w
2
w_2
w2会和它的根一起出栈,因此这种情况我们的算法不会更新
L
O
W
L
I
N
K
(
w
1
)
LOWLINK(w_1)
LOWLINK(w1)和
L
O
W
L
I
N
K
(
v
)
LOWLINK(v)
LOWLINK(v)(这种情况只会出现在
w
1
⇢
w
2
w_1\dashrightarrow w_2
w1⇢w2是一条
c
r
o
s
s
−
l
i
n
k
cross-link
cross−link的时候)。
如果
u
u
u和
w
2
w_2
w2在同一个
S
C
C
SCC
SCC中,那么就满足了我们要求的条件,并且由于
u
u
u还没有返回,根一定是
u
u
u的祖先节点,所以
w
2
w_2
w2一定没有出栈,我们会在
L
O
W
L
I
N
K
(
w
1
)
LOWLINK(w_1)
LOWLINK(w1)中更新,进而会更新
L
O
W
L
I
N
K
(
v
)
LOWLINK(v)
LOWLINK(v)。
综上我们的算法会正确计算
L
O
W
L
I
N
K
LOWLINK
LOWLINK,进而会正确判断根,进而会正确得出所有的
S
C
C
SCC
SCC。
以上就是tarjan的论文的大多数内容了…说实话知道这种级别的dalao也会犯错误的时候,不知道为什么,还挺开心的(虽然读的时候非常非常非常痛苦…如某个图少一条边…)。
下面是我自己补充的关于割边(边双连通分量)的内容。
引理15: 对于无向连通图 G G G, P P P是有向化 G G G中的边后形成的palm树, T T T是 P P P的生成树,对于 ( u , v ) ∈ T (u,v)\in T (u,v)∈T, ( u , v ) (u,v) (u,v)是割边与 L O W P T ( v ) ≧ v LOWPT(v) \geqq v LOWPT(v)≧v等价。
首先我们要明白一件事情,就是 f r o n d frond frond不可能成为割边,这是显然的,所以我们只需要关心 T T T中的边。
我们先来证明充分性,如果 L O W P T ( v ) ≧ v LOWPT(v)\geqq v LOWPT(v)≧v,那么显然从 v v v出发的所有路径如果不经过 ( u , v ) (u,v) (u,v),那么终点就只能在 T v T_v Tv中。删去 ( u , v ) (u,v) (u,v)会导致 u u u和 v v v断连,得证。
然后是必要性, ( u , v ) (u,v) (u,v)是割边,即删去它后导致图不再连通,我们知道删去 ( u , v ) (u,v) (u,v)不会影响 T v T_v Tv外的点之间和 T v T_v Tv内部之间的连通性,所以必然会形成分别由 T v T_v Tv和其他点组成的两个连通分量。即从 v v v出发的路径终点只能在 T v T_v Tv,即会使得 L O W P T ( v ) ≧ v LOWPT(v)\geqq v LOWPT(v)≧v,得证。
综上,我们得到了判断一条边是否是割边的充要条件。
写这篇的主要目的是加深对tarjan算法的理解,看tarjan主要是因为之前看朱刘算法要用,但是我一次都没有真正弄明白过tarjan…所以就花了好几天看"没用"的"小"论文(就结果来说,我想我一辈子都不会忘了tarjan是怎么回事了,虽然还是不能倒着写hhh
其实上述都是去年写的内容了,之前是放在我自己的博客上的,但是因为一些原因我需要先把博客关掉。但是我自己会时不时拿自己的博客进行复习,没了博客复习不便,所以就准备全部搬到csdn上,其实上个月中旬就打算开始搬结果拖到了现在…
之前是hexo+mathjax写的,公式写法是用自己写的脚本转过来的,有可能有没注意到或是没想到的地方,有错误的地方还请指出。