跳到主要内容

Strongly connected components (SCC)

Definition

Stronly connected component(SCC)的定義為component中任兩個點都相連(connected)。

Pseudo code

STRONGLY-CONNECTED-COMPONENTS(G)
1. call DFS(G) to compute finish times u.f for each vertex u
2. create G^T
3. call DFS(G^T), but in the main loop of DFS, consider the vertices in order of decreasing u.f (as computed in line 1)
4. output the vertices of each tree in the depth-first forest in line 3 as a separate strongly connected component

Proofs

Lemma 1

Statement: Let CC and CC' be distinct strongly connected components in directed graph G=(V,E) G = (V, E),let u,vCu', v' \in C', and suppose that GG contains a path upu u\overset p\leadsto u'. Then G cannot conotain a path vpvv'\overset p \leadsto v

Proof:

如果vpvv'\overset p \leadsto v,則存在路徑uuvu\leadsto u' \leadsto v'vvuv' \leadsto v \leadsto u,代表uu可以reach到 vv',且vv'也可以到uu,與假設 CC, CC'是兩個SCC矛盾。

Lemma 2

Statement: Let CC and CC' be distinct strongly connected components in directed graph G=(V,E) G = (V, E)。Suppose, that there is an edge (u,v)E(u, v) \in E, where uCu \in C' and vCv \in C. Then f(C)>f(C)f(C') > f(C)

Proof: 我們假設兩個情況:

  1. CC'先被探訪
  2. CC先被探訪

在第一種情況,因為edge (u,v)(u, v),所以CC必定先被玩成 uu才會完成,故f(C)>f(C)f(C') > f(C)。在第二種情況,因為不存在路徑使CC可以reach CC',所以CC先完成,然後才探訪CC',故f(C)>f(C)f(C') > f(C)

由以上兩種狀況可得證, f(C)>f(C)f(C') > f(C)

Corollary

Statement: Let CC and CC' be distinct strongly connected components in directed graph G=(V,E) G = (V, E),and suppose that f(C)>f(C)f(C) > f(C'). Then ETE^T contains no edge (v,u)ET(v, u) \in E^T such that uCu \in C' and vCv \in C

Proof:

根據Lemma 1,如果ETE^T含有edge (v,u)(v, u),則代表EE含有edge (u,v)(u, v),而這與Lemma 2矛盾,所以GTG^T不含有edge (u,v)(u, v)

Theorem 1

Statement: The STRONGLY-CONNECTED-COMPONENTS procedure correctly computes the strongly connected components of the directed graph G provided as its input。

Proof:

我們可以用induction來證明。假設圖有kk個SCC,當k=0k = 0很明顯得證,假設k=nk = n的時候也成立,我們嘗試證明k=n+1k = n+1也成立。

根據STRONGLY-CONNECTED-COMPONENTS line 3,我們根據v.fv.f的值,由大到小在GTG^T做DFS。而根據Lemma 2,如果f(C)>f(C)f(C') > f(C),代表GG存在一個edge (u,v)(u, v)uC,vCu \in C', v \in C,或是兩個SCC之間沒有edge存在。

不管是何種情況,在GTG^T都不存在一組edge (v,u)(v, u), uC,vCu \in C', v \in C,因此DFS走訪完第k+1個scc後也會停止,故k=n+1k = n+1也成立,故得證。

Time complexity

Θ\Theta(V+EV + E)。在line 1做了一次DFS,take Θ\Theta(V+EV + E),在line 2將圖transpose,也take Θ\Theta(V+EV + E),line 3又做了一次DFS,所以total的time complexity是 Θ\Theta(V+EV + E)。

Golang implementation

scc.go
import "fmt"

type Vertex struct {
finished bool
idx int
}

type Graph struct {
edges map[int][]Vertex
vertices map[int]Vertex
}

// dfsUtil returns vertices with finish in reverse order
func dfsUtil(g Graph, v Vertex, result []Vertex) []Vertex{
for _, neighbor := range g.edges[v.idx] {
// has been visited
if neighbor.finished {
continue
}

result = dfsUtil(g, neighbor, result)
}

v.finished = true
result = append([]Vertex{v}, result...)

return result
}

func transpose(g Graph) Graph {
gTranspose := Graph{
edges: map[int][]Vertex{}
}

// init vertices
for vIdx := range g.vertices {
gTranspose.vertices[vIdx] = Vertex {
idx: vIdx,
}
}

// build edges
for vIdx, edges := range g.edges {
v := gTranspose.vertices[vIdx]

for _, neighbor := range edges {
if _, ok := gTranspose.edges[neighbor.idx] {
gTranspose.edges[neighbor.idx] = []Vertex{}
}

// reverse the edge direction
gTranspose.edges[neighbor.idx] = append(gTranspose.edges[neighbor.idx], v)
}
}

return gTranspose
}

func sccUtil(g, gTranspose Graph, v Vertex, sccResults []Graph, sccComponent *Graph) []Graph {
var shouldAddToResult bool
if sccComponent == nil {
sccComponent = &Graph{}
shouldAddToResult = true
}

for _, neighbor := range gTranspose.edges[v.idx] {
if neighbor.finished {
continue
}

sccResults = sccUtil(g, neighbor, sccResults, sccComponent)
}

neighbor.finsihed = true

// add the vertex and its edges to the SCC component
sccComponent.vertices[v.idx] = v
sccComponent.edges[v.idx] = []Vertex{}
for _, edge := range g.edges[v.idx] {
sccComponent.edges[v.idx] = append(sccComponent.edges[v.idx], edge)
}

if shouldAddToResult {
sccResult = append(sccResult, *sccComponent)
}

return sccResult
}

func SCC(g Graph) []Vertex {
// do DFS to g
verticesInReverseVisitedOrder := []Vertex{}

for vIdx, v := range g.vertices {
if v.finished {
continue
}

verticesInReverseVisitedOrder = dfsUtil(g, vIdx, verticesInReverseVisitedOrder)
}

// compose graph transpose
gTranspose := transpose(g)

// do dfs to gTranspose with finish time in reverse order
results := []Graph{}
for _, v := range verticesInReverseVisitedOrder {
vInTranspose := gTranspose.vertices[v.idx]

if vInTranspose.finished {
continue
}

results = sccUtil(g, gTranspose, results, nil)
}

return results
}