Skip to content

Commit a802cb1

Browse files
committed
dfs connectivity
1 parent 4102634 commit a802cb1

1 file changed

Lines changed: 156 additions & 0 deletions

File tree

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# Finding Connected Components with DFS
2+
3+
Let's modify the DFS algorithm to label each connected component
4+
5+
6+
## Undirected
7+
8+
```
9+
// map from vertex to its connected component number
10+
cc_num: map[Vertex, int] = {}
11+
prev: map[Vertex, Vertex] = {}
12+
seen: map[Vertex, bool] = {}
13+
14+
function DFS(G=(V, E)):
15+
cc = 0 // connected component index
16+
forall v in V:
17+
seen[v] = false
18+
prev[v] = NULL
19+
20+
forall v in V:
21+
if not seen[v]:
22+
cc += 1
23+
Explore(v, cc)
24+
25+
function Explore(z: Vertex, cc: int):
26+
cc_num[z] = cc
27+
seen[z] = True
28+
forall w in z.neighbors:
29+
if w is NEW:
30+
Explore(w)
31+
prev[w] = z
32+
```
33+
34+
## Directed
35+
36+
```
37+
// map from vertex to its connected component number
38+
39+
pre: map[Vertex, int] = {} // pre-order number
40+
post: map[Vertex, int] = {} // post-order number of a vertex
41+
prev: map[Vertex, Vertex] = {}
42+
seen: map[Vertex, bool] = {}
43+
44+
function DFS(G=(V, E)):
45+
clock = 1
46+
forall v in V:
47+
seen[v] = false
48+
prev[v] = NULL
49+
50+
forall v in V:
51+
if not seen[v]:
52+
cc += 1
53+
Explore(v, cc)
54+
55+
function Explore(z: Vertex):
56+
pre[z] = clock
57+
clock += 1
58+
59+
seen[z] = True
60+
forall w in z.neighbors:
61+
if w is NEW:
62+
Explore(w)
63+
prev[w] = z
64+
65+
post[z] = clock
66+
clock += 1
67+
```
68+
69+
**Post order numbers are the useful ones.**
70+
71+
## Edge Classification
72+
73+
For an edge $z\to w$:
74+
- Tree edge: `post[z] > post[w]`
75+
- Back edge: `post[z] < post[w]`
76+
- Forward edge: `post[z] > post[w]`
77+
- Forward edges can jump multiple levels down the tree
78+
- Tree edges go down 1 at a time (new discoveries)
79+
- Cross edge: `post[z] > post[w]`
80+
- These edges happen across nodes that don't share ancestors
81+
82+
## Cycles
83+
84+
**$G$ has a cycle *iff* the DFS tree has a back edge.**
85+
86+
## Topological Sort
87+
88+
This applies to directed acyclic graphs (DAG). A DAG is topologically sorted when all vertices are arranged such that all edges go from vertices with **higher `post` to lower ones**.
89+
90+
To sort a DAG, run DFS and sort vertices by `post` in descending order. Sorting here takes $O(V)$ because we don't need comparison sort, DFS takes $O(V+E)$.
91+
92+
Source vertex has the highest `post`, sink vertex has the lowest `post`
93+
94+
### Alternative topological sort
95+
96+
1. Find a sink vertex, output and delete it from $G$
97+
2. Repeat (1) until the graph is empty
98+
99+
## General Directed Graphs
100+
101+
### Strongly Connected Components (SCC)
102+
103+
Vertices $v, w\in V$ are strongly connected if there's a path $v \rightsquigarrow w$ and $w \rightsquigarrow v$.
104+
105+
A strongly connected component is the maximal set of strongly connected vertices. There can be multiple SCCs in a single graph.
106+
107+
### Metagraph
108+
109+
Treat each SCC as a single vertex. If there's any edge in $E$ that connects 2 SCCs, then there's an metagraph edge between them.
110+
111+
**The metagraph of SCCs is always a DAG.** If there's a cycle between 2 SCCs $S$ and $S'$, then they are just subgraphs of the same SCC $S\cup S'$.
112+
113+
**Every arbitrary directed graph is a DAG of its SCCs.**
114+
115+
## Use 2 DFS runs to find all SCCs
116+
117+
### Basic Idea
118+
119+
Observe that the metagraph must have a sink and a source. This allows us to use [alternative topological sort](#alternative-topological-sort), but on SCCs.
120+
121+
1. Find a sink SCC, remove
122+
2. Repeat (1) until G is empty
123+
124+
In this case we want to use **sink** because we can easily find it as long as we have a sink vertex `z` from the original graph. Run `Explore(z)` from it, then collecting everything reachable from `z` gives us the sink SCC.
125+
- Source won't work like this because all vertices in the source SCC can reach all other SCCs, then we have no way to know when did we leave the source SCC.
126+
127+
### Guaranteed sink?
128+
129+
**$v$ with the lowest `post` is NOT always in the sink SCC.** But $v$ with the highest `post` is in a source SCC.
130+
131+
Idea: flip all the edges to turn the sink SCC into a source SCC.
132+
133+
For $G = (V, E)$, the reverse is $G^R = (V, E^R)$.
134+
135+
$$
136+
E^R = \{wv: vw\in E\}
137+
$$
138+
139+
source SCC in $G$ = sink SCC in $G^R$
140+
sink SCC in $G$ = source SCC in $G^R$
141+
142+
So we just need the vertex with the highest `post` in $G^R$. That vertex is the sink in $G$.
143+
144+
## Final SCC Algorithm
145+
146+
```
147+
function SCC(G=(V, E)):
148+
build G_R = (V, E_R)
149+
post_R = DFS(G_R)
150+
sort V descending by post_R
151+
cc_num = undirected_DFS(G)
152+
```
153+
154+
The `cc_num` will now have the connected component number of each vertex.
155+
156+

0 commit comments

Comments
 (0)