算法 {Tarjan求强连通分量SCC}
@LOC: 3
有向图-SCC強聯通分量
定義
#SCC強聯通分量Strongly Connected Component
#
有向圖G的所有點 劃分為若干個不相交的子集S1,S2,...
, Si
對應的導出子圖是強聯通的;
G的所有邊 分為2類: {1:[在Si
的導出子圖裡]; 2:[形如Si->Sj
]};
相關定義
#連通子圖#
有向圖G, 對於其子圖SG 如果他是連通的(這裡的連通 是泛型的 你可以定義為{強/半/弱})(即此時單獨考慮SG 與G無關) 則SG為G的連通子圖;
#連通分量CCConnected Component
(連通塊)#
極大的連通子圖(這裡的連通 是泛型的 你可以定義為{強/半/弱}) 為連通分量; 有向圖G的所有連通子圖 在包含關係下為偏序關係, 其所有極大元 為G的所有連通分量;
連通分量 一定是導出子圖, 因為對於連通分量CC 增加其內部的邊 不會影響CC的連通性; 因此 連通分量可以直接用(點集)來表示;
舉例
G: (a->b->c) (d->e)
, a->b
為連通子圖, a->b->c
是連通分量(當然也是連通子圖);
@DELI;
#強聯通SCStrongly Connceted
#
對於有向圖G, 任意兩個點a,b
是強連通的
⟺
\iff
⟺ (a可達b) 且 (b也可達a);
@DELI;
#有向圖是強連通的#
有向圖G, 如果(G的任意兩個點 都是強連通的)
⟹
\implies
⟹ G是強連通的;
@DELI;
#有向圖中兩點間的連通性#
對於有向圖G, 任意兩個點a,b
是連通的
⟺
\iff
⟺ (a可達b) 或 (b可達a);
@DELI;
#SCC的縮點#
令有向圖DAG滿足: 原圖G中的任意邊a->b
, 如果Scc_id[a] != Scc_id[b]
, 則他對應在圖DAG中為Scc_id[a] -> Scc_id[b]
的一條有向邊;
即 可以想象為: 原圖G中的一個SCC(他由很多點組成) 縮成了一個DAG中的一個點;
性質
@DELI;
對於一個強聯通的有向圖G, G裡面的兩個點a,b
其滿足: 刪除原圖G中的任何一條有向邊後 a,b
仍然是連通的(只不過 可能不是強連通了, 比如原來是a可達b && b可達a
而現在變成了a可達b 但b不可達a
);
算法
模板代碼
namespace ___Tarjan_SCC{
vector<int> SCC_id; // `SCC_id[a]=b`: 圖中的`a`點所在的SCC(強聯通塊)的ID號為`b`(滿足`0<=b<SCC_count`);
vector<int> SCC_size; // `SCC_size[a]=b`: ID號為`a`的SCC 他由`b`個點組成; `SCC_size.size()`為SCC的個數;
stack<int> __Stk;
vector<int> __DfsId, __LowId;
int __DfsIdCounter;
const ___Graph * __DiG; // 要處理的*有向圖*;
void __Dfs( int _cur){
__Stk.push(_cur); __DfsId[ _cur] = __DfsIdCounter ++; __LowId[_cur] = __DfsId[_cur];
for( int nex, edge = __DiG->Head[ _cur]; ~edge; edge = __DiG->Next[ edge]){
nex = __DiG->Vertex[ edge];
if( -1 == __DfsId[ nex]){ __Dfs( nex);}
if( -1 == SCC_id[ nex]){ MinSelf_( __LowId[ _cur], __LowId[ nex]);}
}
if( __DfsId[ _cur] == __LowId[ _cur]){
SCC_size.push_back( 0);
while( true){
int temp = __Stk.top(); __Stk.pop();
SCC_id[ temp] = SCC_size.size() - 1; SCC_size.back() ++;
if( temp == _cur){ break;}
}
}
}
void Work( const ___Graph & _DiG){
__DiG = &_DiG;
SCC_id.resize( __DiG->PointsCount); memset( SCC_id.data(), -1, sizeof( SCC_id[0]) * SCC_id.size()); SCC_size.clear();
while( false == __Stk.empty()){ __Stk.pop();}
__DfsId.resize( __DiG->PointsCount); __LowId.resize( __DiG->PointsCount); memset( __DfsId.data(), -1, sizeof( __DfsId[0]) * __DfsId.size());
__DfsIdCounter = 0;
for( int i = 0; i < __DiG->PointsCount; ++i){
if( -1 == __DfsId[ i]){ __Dfs( i);}
}
}
void Build_DAG( ___Graph & _dag){
//< *SCC的縮點*;
_dag.Initialize( SCC_size.size());
for( int cur = 0; cur < __DiG->PointsCount; ++cur){
for( int nex, edge = __DiG->Head[cur]; ~edge; edge = __DiG->Next[edge]){
nex = __DiG->Vertex[ edge];
if( SCC_id[ cur] == SCC_id[ nex]){ continue;}
_dag.Add_edge( SCC_id[ cur], SCC_id[ nex], __DiG->Weight[ edge]);
}
}
}
} // namespace ___Tarjan_SCC
注釋
SCC算法 依靠的核心是: 一個SCC(點集) 在DFS樹中 是一個子樹(這裡不是說的以x為根的子樹, 而是子樹 他的定義是子樹是一個點集S, S中任何兩點 在原樹中的路徑上的所有點 都在S中), 因為子樹 一定只有1個(樹根) 也就是最小時間戳; 即當DFS回溯到這個(子樹的樹根)時 此時stack裡頂層的連續一段存儲的 就是這個子樹 即這個割集 即stack形如[bottom..., SCC]
;
@DELI;
LowId
是Tarjan
算法的核心;
他的定義: 對於一個SCC 令其最小的DfsId[st]
為mi
, 則我們的目的是 對任意
x
∈
S
C
C
x \in SCC
x∈SCC
.
1(x==st
):[LowId[st]=mi
];
.
2(x!=st
):[LowId[x] != DfsId[t]
, 且t \in SCC, DfsId[t] < DfsId[x]
]; 換句話說, 此時x的LowId
值 等於該SCC內某個點t的時間戳 且滿足t的時間戳是小於DfsId[x]
的;
也就是, 只要不是在st
這個點 那麼他的LowId
一定不等於他的DfsId
(即這個點 仍然會留在stack
裡面), 只有當在st
這個點時 才會有LowId = DfsId
(然後此時stack=[... st, others]
);
比如0<->1<->2
(先遍歷1->2
然後才是1->0
), 最終會得到Low[0,1] = 0, Low[2]=1
;
.
因此 代碼中的MinSelf_( __LowId[ _cur], __LowId[ nex]);
你寫成DfsId[nex]
也可以, 你寫成LowId[nex]
並不能保證 一個SCC裡的所有點的LowId
值都相同, 比如上面這個樣例;
@DELI;
對於當前的Dfs(cur)
中的一條邊cur->nex
有如下幾種情況:
for( int nex, edge = __DiG->Head[ _cur]; ~edge; edge = __DiG->Next[ edge]){
nex = __DiG->Vertex[ edge];
if( -1 == __DfsId[ nex]){
__Dfs( nex);
if( SCC_id[nex] != -1): *第1類邊*;
else: *第2類邊*;
}
else{
if( `nex,cur`不是同一個*DFS樹*上): *第3類邊*;
elif( `SCC_id[nex] != -1`): *第4類邊*;
else: *第5類邊*;
}
}
`SCC_id[x]!=-1`等價於`IsInStk[x]=false`;
1(DfsId[nex] == -1
): 即此時要進行Dfs(nex)
, 即這條邊是DFS樹上邊, 此時分2種情況:
.
第1類邊; @IF(SCC_id[nex] != -1)
):[ cur,nex
不在同一個SCC裡, 當前邊是橫跨2個SCC的];
.
第2類邊;@ELSE:[cur,nex
在同一個SCC裡, 此時要執行MinSelf( LowId[cur], LowId[nex])
; 比如0->1->2->0
, Dfs(2)
之後此時Low[2]=0
回溯到Dfs(1)
時 更新Low[1]=0
;];
2(DfsId[nex] != -1
): 此時 即Dfs(nex)
先前已經調用過了 此時分為3種情況:
.
第3類邊;@IF(nex
不是當前DFS樹裡的節點):[ 比如0, 1->0
第一次的DFS樹是0
第二次的DFS樹是1
, 此時訪問到(先前的DFS樹)上的節點 而先前的DFS樹 他的SCC都已經確定了; 因此 cur,nex
不在同一個SCC裡, 當前邊是橫跨2個SCC的];
.
第4類邊; @ELIF(SCC_id[nex]!=-1
):[ nex
是當前DFS樹的節點 但是他所在的SCC已經確定; 比如0->1, 0->2->1
對於Dfs(2)
時的2->1
這條邊 此時1
所在的SCC{1}
已經確定; 跟上面@IF
的情況一樣 cur|nex
不會在同一個SCC裡];
.
第5類邊; @ELSE:[SCC_id[nex]==-1
):[ nex
是當前DFS樹的節點 且此時nex
是DFS樹上的cur
的祖先節點, 即存在一條nex->...->cur
的路徑(也就是當前的DFS棧是形如[...nex,...,cur]
) 然後當前又出現了一條cur->nex
的路徑, 此時形成了一個環,因此cur,nex
這個環 一定是強聯通的 因此更新MinSelf( LowId[cur], LowId[nex])
;]
性質
例题
@LINK: https://blog.csdn.net/qq_66485519/article/details/128474214
;
筆記
For a (directed) graph G = ( V , E ) G = (V,E) G=(V,E), let I d [ v ] Id[v] Id[v] is the SCC-ID of v ∈ V v \in V v∈V which within the range [0, SCC-Count).
A DFS-Order-Sequence of a graph is [ 0 , 1 , 2 , . . . , n ) [0, 1, 2, ..., n) [0,1,2,...,n), we use O r d e r [ v ] Order[v] Order[v] represents its DFS-Order.
Let
L
o
w
[
v
]
Low[v]
Low[v] denotes a DFS-Order such that
L
o
w
[
v
]
=
O
r
d
e
r
[
w
]
Low[v] = Order[w]
Low[v]=Order[w] such that
O
r
d
e
r
[
w
]
≤
O
r
d
e
r
[
v
]
Order[w] \leq Order[v]
Order[w]≤Order[v] and
w
,
v
w,v
w,v are in the same SCC
.
If there exists a point
w
w
w such that
O
r
d
e
r
[
w
]
<
O
r
d
e
r
[
v
]
Order[w] < Order[v]
Order[w]<Order[v] and
w
,
v
w,v
w,v in the same SCC, then
L
o
w
[
v
]
<
O
r
d
e
r
[
v
]
Low[v] < Order[v]
Low[v]<Order[v]; otherwise
L
o
w
[
v
]
=
O
r
d
e
r
[
v
]
Low[v] = Order[v]
Low[v]=Order[v] if and only if
O
r
d
e
r
[
v
]
Order[v]
Order[v] is the minimal-Order in its SCC (every SCC has exactly one such point, we call such a point The Root of SCC).
+
e.g.,
G
=
(
1
→
2
)
(
2
→
3
)
(
3
→
4
)
(
4
→
3
)
(
2
→
5
)
(
5
→
2
)
(
2
→
1
)
G = (1\to 2)(2\to 3)(3\to 4)(4\to 3)(2\to 5)(5\to 2)(2\to 1)
G=(1→2)(2→3)(3→4)(4→3)(2→5)(5→2)(2→1) (note the order of edges is also the process of DFS(1)), then we have
2
2
2 SCC
(
1
,
2
,
5
)
(
3
,
4
)
(1,2,5) (3,4)
(1,2,5)(3,4); finally,
L
o
w
[
1
]
=
0
,
L
o
w
[
2
]
=
0
,
L
o
w
[
5
]
=
1
Low[1] = 0, Low[2] = 0, Low[5]=1
Low[1]=0,Low[2]=0,Low[5]=1, all
L
o
w
[
v
]
Low[v]
Low[v] in a SCC maybe not the same;
L
o
w
[
2
]
=
0
Low[2] = 0
Low[2]=0 cuz there is
O
r
d
e
r
[
1
]
=
0
<
O
r
d
e
r
[
2
]
=
1
Order[1] = 0 < Order[2]=1
Order[1]=0<Order[2]=1;
L
o
w
[
5
]
=
1
Low[5] = 1
Low[5]=1 cuz there is
O
r
d
e
r
[
2
]
=
1
<
O
r
d
e
r
[
5
]
=
4
Order[2] = 1 < Order[5]=4
Order[2]=1<Order[5]=4 (
L
o
w
[
5
]
Low[5]
Low[5] can also equals
O
r
d
e
r
[
1
]
=
0
Order[1] = 0
Order[1]=0);
0
0
0 is the root of the SCC
1
,
2
,
5
1,2,5
1,2,5)
When we at
D
f
s
(
c
u
r
)
Dfs( cur)
Dfs(cur) and iterating its adjacent-points
n
e
x
nex
nex, there are several steps:
+
Firstly, we set Order[cur] = Low[cur] = DFS_order ++
(cuz Low[cur] <= Order[cur]
finally, so we set it equals to Order[cur]
initially)
+
If nex
has never been called by DFS, so we call
D
f
s
(
n
e
x
)
Dfs(nex)
Dfs(nex); and then we update
L
o
w
[
c
u
r
]
=
m
i
n
(
s
e
l
f
,
L
o
w
[
n
e
x
]
)
Low[cur] = min(self, Low[nex])
Low[cur]=min(self,Low[nex]) (cuz nex
never be DFS visited, if
c
u
r
,
n
e
x
cur, nex
cur,nex is not in the same SCC and
O
r
d
e
r
[
n
e
x
]
>
O
r
d
e
r
[
c
u
r
]
Order[nex] > Order[cur]
Order[nex]>Order[cur], we have
n
e
x
nex
nex must the Root of its SCC, so
L
o
w
[
n
e
x
]
>
O
r
d
e
r
[
c
u
r
]
Low[nex] > Order[cur]
Low[nex]>Order[cur]; this operation is ineffective), otherwise
c
u
r
,
n
e
x
cur,nex
cur,nex in the same SCC, if
c
u
r
cur
cur is the Root then
L
o
w
[
n
e
x
]
=
O
r
d
e
r
[
c
u
r
]
Low[nex] = Order[cur]
Low[nex]=Order[cur], so this operations is also ineffective; if
c
u
r
cur
cur is not the Root, there must exists a point
n
e
x
1
nex1
nex1 whose
L
o
w
[
n
e
x
1
]
<
O
r
d
e
r
[
c
u
r
]
Low[nex1] < Order[cur]
Low[nex1]<Order[cur] (cuz there must has a path
c
u
r
→
n
e
x
1
→
.
.
→
r
o
o
t
cur \to nex1 \to .. \to root
cur→nex1→..→root where
.
.
.
...
... do not contains
c
u
r
cur
cur, therefore
L
o
w
[
n
e
x
1
]
=
O
r
d
e
r
[
r
o
o
t
]
<
O
r
d
e
r
[
c
u
r
]
Low[nex1] = Order[root] < Order[cur]
Low[nex1]=Order[root]<Order[cur]);
+
When DFS returns to a root-point Low[] = Order[]
, then the DFS-Stack must be in the form [...., root, S]
where any point that in the same SCC with root
, must occurs in S
;
Code
void dfs( int _cur){
queue[ tail ++] = _cur;
isInQueue[ _cur] = true;
dfsOrderId[ _cur] = dfsOrderId_counter ++;
lowest_orderId[ _cur] = dfsOrderId[ _cur];
//--
for( int nex, i = graph->Head[ _cur]; ~i; i = graph->Next[ i]){
nex = graph->Vertex[ i];
if( -1 == dfsOrderId[ nex]){ //< never `dfs( nex)` called
dfs( nex);
}
if( isInQueue[ nex]){ //< `nex` maybe already forms a SCC, we should aviod this case; e.g., (1<->2)<-3, so Low[2] cann't be used to updated Low[3]
lowest_orderId[ _cur] = min( lowest_orderId[ nex], lowest_orderId[ _cur]);
}
}
if( lowest_orderId[ _cur] == dfsOrderId[ _cur]){
scc_size[ scc_id_counter] = 0;
while( true){
int c = queue[ tail - 1];
isInQueue[ c] = false;
-- tail;
scc_id[ c] = scc_id_counter;
++ scc_size[ scc_id_counter];
if( c == _cur){ break;}
}
++ scc_id_counter;
}
}
Actually, the array dfsOrderId, lowest_orderId
can be omitted; (use scc_id
to denote lowest_orderId
, cuz when DFS not yet returned to root
, the array scc_id
is free; and once a SCC is fixed (i.e., DFS has returned to its root
, any other points (not belongs to this SCC) would not visit/use any information of this SCC)
void dfs( int _cur){
ptrNew_queue[ queue_tail ++] = _cur;
ptrNew_isInQueue[ _cur] = true;
int current_dfsOrderId = dfsOrderId_counter;
ptrNew_sccId[ _cur] = dfsOrderId_counter ++;
//--
for( int nex, i = ptrRef_graph->Head[ _cur]; ~i; i = ptrRef_graph->Next[ i]){
nex = ptrRef_graph->Vertex[ i];
if( -1 == ptrNew_sccId[ nex]){ //< never `dfs( nex)` called
dfs( nex);
}
if( ptrNew_isInQueue[ nex]){
ptrNew_sccId[ _cur] = min( ptrNew_sccId[ nex], ptrNew_sccId[ _cur]);
}
}
if( ptrNew_sccId[ _cur] == current_dfsOrderId){
ptrNew_sccSize[ scc_id_counter] = 0;
while( true){
int c = ptrNew_queue[ queue_tail - 1];
ptrNew_isInQueue[ c] = false;
-- queue_tail;
ptrNew_sccId[ c] = scc_id_counter;
++ ptrNew_sccSize[ scc_id_counter];
if( c == _cur){ break;}
}
++ scc_id_counter;
}
}