0% found this document useful (0 votes)
10 views28 pages

Depth First Search

The document provides an overview of graph theory, focusing on depth-first search (DFS) and its applications in undirected and directed graphs. It discusses graph representations, path search problems, cycle detection, topological ordering, and algorithms for identifying strongly connected components. Additionally, it covers concepts like bridges, cut vertices, and the limitations of recursive DFS implementations.

Uploaded by

reankera9
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views28 pages

Depth First Search

The document provides an overview of graph theory, focusing on depth-first search (DFS) and its applications in undirected and directed graphs. It discusses graph representations, path search problems, cycle detection, topological ordering, and algorithms for identifying strongly connected components. Additionally, it covers concepts like bridges, cut vertices, and the limitations of recursive DFS implementations.

Uploaded by

reankera9
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 28

Depth-First Search

Nikolay Kalinin | Harbour.Space, 24.03.2025


Undirected Graphs
● An undirected graph is an ordered pair G = (V, E), where V is a set of
vertices (nodes) and E is a set of 2-elements subsets of V called edges.
Usually, 0 < |V| < ∞. Edges are denoted as (u,v).
● Vertex degree is the number of adjacent vertices.
● Path is a sequence of vertices, connected consistently.
● V = {0, 1, 2, 3, 4, 5, 6}
● E = {(0,2), (0,6), (2,1), (2,6), (3,5)}
● d(0)=2, deg(1)=1, deg(2)=3, ...., deg(4)=0.
● [2,6,0,2,1] is a path.
Directed Graphs
● A directed graph is an ordered pair G = (V, E), where V is a set of vertices
(nodes) and E is a set of ordered pairs over V called edges (arcs).
Usually, 0 < |V| < ∞. Edges are denoted as (u,v).
● Vertex out-degree is the number of outgoing edges,
The in-degree is the number of ingoing edges.
● Path is a sequence of vertices, connected consistently.
● V = {0, 1, 2, 3, 4, 5}
● E = {(0,1), (1,2), (1,3), (2,0), (2,4), (4,2), (5,5)}
● out_d(0)=1, out_d(1)=2, out_d(3)=0, ..., out_d(5)=1.
in_deg(0)=in_deg(1)=1.
● [4,2,0,1,2] is a path.
Storing graphs
● Adjacency matrix: A[u][v]=1 if there is an edge (u, v).
○ O(n2) space. O(n) time to traverse adjacent vertices. O(1) time to check if an edge exists.
● Adjacency lists: edges[v] is a list (vector) of all edges from v.
○ O(m) space. O(d(v)) time to traverse adjacent vertices. O(d(v)) time to check if an edge exists.
Path Search Problem
● Given a graph G (directed or undirected) and two vertices s and t. Check if a
path from s to t exists? If a path exists, find and print any path from s to t.
● We need some strategy to traverse the graph, visiting all the vertices
reachable from s.
● Two popular strategies (algorithms): depth-first search (DFS) and breadth-first
search (BFS).
● DFS: explore the graph locally: in each step, we move to an unvisited
neighboring node. If there are none, we move back.
● BFS: explore the graph in all directions simultaneously, in parallel. Like fire or
flood spreads.
Path Tree
● A tree is a connected graph without cycles (considering undirected edges).
● The path tree is a tree rooted at s that covers all vertices reachable from s.
● Let parent[v] be the previous vertex on the path from s to v.
○ parent[s] = s,
○ parent[u] = None for unreachable vertices u.
● The parent array allows restoring the path from s to any vertex.

function pathTo(t):
path := []
while parent[t] != t:
path.push(t)
t = parent[t]
path.push(t)
reverse(path)
return path parent = [0,3,1,0,1,2,-1,-1,4]
Depth-first search (DFS)
● We define the dfs(cur) function that traverses all not-yet-visited vertices
reachable from cur without going through visited, and then returns.
● To do that, we check all unvisited neighbors to and call dfs(to) recursively.
● We have a global array to check is a vertex is visited.
● Running time is O(|V|+|E|).
To traverse the whole graph
function DFS(cur):
regardless of connectivity from s,
visited[cur] := true use a series of DFS calls:
for to in neighbors(cur):
visited[] ← false
if not visited[to]:
for s in V:
DFS(to) if not visited[s]:
DFS(s)
DFS: parents, three colors and in-out times
● Define three colors of vertices function DFS(cur, p):
○ WHITE: the vertex is not visited yet parent[cur] := p
○ GRAY: we entered the vertex, but not yet exited color[cur] := GRAY
(the call dfs(v) is in the stack) tin[cur] := T++
○ BLACK: we exited the vertex for to in neighbors(cur):
● visited[v] == (color[v] != WHITE) if color[to] == WHITE:
DFS(to, cur)
● The starting node s and the colors of all vertices
color[cur] := BLACK
are enough to continue the DFS tout[cur] := T++
● Define two values for each vertex
○ tin[v]: time when we entered v main:
(its color changes from WHITE to GRAY) parent[] ← -1
○ tout[v]: time when we exited v color[] ← WHITE
(its color changes from GRAY to BLACK) DFS(s, s)
DFS example
Example (s=0):
DFS example
Example (s=0):


Time Segments
● For any two vertices u and v, time segments [tin[u], tout[u]],
[tin[v], tout[v]] give the relation of u and v in the DFS-tree:
○ [tin[u], tout[u]] nested in [tin[v], tout[v]] denotes u being a descendant of v,

○ [tin[v], tout[v]] nested in [tin[u], tout[u]] is vice versa,

○ [tin[v], tout[v]] not intersecting [tin[u], tout[u]] denotes u and v being not
comparable.

○ No other case is possible.


Edge Types
● Group edges by the vertices color when we first look at the edge.
○ → Tree edges: from GRAY to WHITE
○ → Backward edges: from GRAY to GRAY
○ → Forward edges: from GRAY to BLACK, to a descendant
○ → Cross edges: from GRAY to BLACK, to a non-comparable
vertex
● To choose between forward or cross, look at the time segments.
● For undirected graphs, forward and cross edges do not exist, only tree and
backward edges exist.
Cycle Detection
● A cycle exists if and only if a backward edge exists, i.e. an edge from the
current vertex to a GRAY vertex (careful with undirected graphs: skip an edge
by which you came).
● For undirected graphs, visited/not visited information is enough, but skip the
parent edge.
● For directed graphs, three colors (WHITE, GRAY, BLACK) are required, no
need to skip the parent.
Topological Ordering
● A topological sorting or topological ordering of a directed graph is a linear
ordering of its vertices such that for every directed edge (u, v), u comes
before v in the ordering.
○ In other words, all edges go left to right.
● Possible topological orderings for graph on the right:
○ [e, h, g, c, b, f, a, d]
○ [g, h, c, e, b, f, d, a]
● Criterion: A topological ordering exists
if and only if the graph is acyclic.
Topological Ordering
● Naive approach. Each time extract a vertex u with in_deg(u)=0, append it
to the ordering, and remove from the graph.
● Linear algorithm. Run a DFS and order the vertices by decreasing of the
tout values.
function topsort(cur):
visited[cur] := true
for to in neighbors(cur):
if not visited[to]:
topsort(to)
order.append(u)

for s in V:
if not visited[s]:
topsort(s)
reverse(order)
Bipartite checking
● An undirected graph is bipartite, if we can color the vertices in two colors, so
that no edge connects two vertices of the same color
● A graph is bipartite if and only if it does not have a cycle of odd length
● Choose the color of any vertex and color the others with a DFS
● On any contradiction, break
● dfs(cur, cur_color):
if visited[cur]:
if color[cur] != cur_color: not bipartite
return
visited[cur] = True, color[cur] = cur_color
for to in neighbors(cur):
dfs(to, cur_color ^ 1)
Bridges
● For an undirected graph, a bridge is an edge of the graph whose deletion
increases the number of connected components.
● Criterion. An edge is a bridge if and only if it does not belong to any simple
cycle.
Bridges
● Let’s run a DFS.
● Only tree edges could be bridges.
● How to check if a tree edge is a bridge?
● Definition. Forward-up value, fup[u] is the smallest
depth we can reach from u by traversing zero or more
tree edges, at the end, zero or one backward edge.
● Depth d[v] is the distance from the root in tree edges.
Bridges
● Example

● fup = [0, 1, 3, 0, 1, 3, 1, 0, 3, 3].


Bridges
● Calculate fup values in O(|V|+|E|) using dynamic programming:
○ fup[v] is the minimum of fup over children of v, and depth over backward edges from v.
● function DFS(cur, parent, cur_depth):
visited[cur] := true
depth[cur] := fup[cur] := cur_depth
for to in neighbors(cur):
if to == parent:
Continue
if not visited[to]:
DFS(to, cur, cur_depth + 1)
fup[cur] := min(fup[cur], fup[to])
else:
fup[cur] := min(fup[cur], depth[to])
Bridges

● Tree edge (u,v) is a bridge if and only if there is no
edge from subtree of v to u or higher.
● In other words, tree edge (u,v) is a bridge if and only if
fup[v]>depth[u].
● We can check that after the return of the recursive call
from v.
● Linear algorithm to find all bridges.
Cut Vertices (Articulation Points)
● For an undirected graph, a cut vertex is a vertex of the graph whose deletion
increases the number of connected components.
● There is no simple connection between bridges and cut points!
Cut Vertices (Articulation Points)

● A vertex u is a cut point if and only if:


○ u is not the root and such tree edge (u,v)
exists that fup[v] ≥ dep[u],
○ u is the root and has at least 2 children in
the DFS-tree.
● Linear algorithm to find all cut vertices.
Strongly Connected Components
● A directed graph is strongly connected if
every vertex is reachable from every other
vertex.
● How to check if the given directed graph is
strongly connected in linear time?
● Run the DFS from any vertex s and check
that all vertices are reachable from s.
● Reverse the graph, run the DFS from the
same vertex s, and check that all vertices are
reachable.
Strongly Connected Components
● A strongly connected component (SCC) of a
directed graph is a maximal subgraph that is
strongly connected.
● There is a unique partition of a directed graph
into SCCs.
● There are several linear algorithms to find
strongly connected components.
Strongly Connected Components
● If each strongly connected component is
contracted to a single vertex, the resulting
graph is a directed acyclic graph called the
condensation of the original graph.
● A directed graph is acyclic if and only if it has
no strongly connected components with more
than one vertex.
Kosaraju's algorithm
● Run a series of DFS to visit all vertices. Order vertices in decreasing time of
their tout values.
○ This is exactly how a topological ordering algorithm works.
● Reverse the graph.
● Run a new series of DFS in the reversed graph. Choose the starting vertices
in the order obtained in the first step.
● Each DFS tree is a single strongly connected component.
● Strongly connected components are found in a topological order.
○ No need to run another topsort on the SCCs.
DFS is a deep recursion
● In most OS and programming languages, recursion is limited, because the
stack size is limited
● The standard implementation of DFS is recursion-heavy, so it can cause stack
overflow (or hit the recursion limit)
● For C++ on Windows add -Wl,--stack=256000000 when compiling
● For Linix, use ulimit -s 256000000 before running
● In python, use sys.setrecursionlimit(5000000)
● In production code implement recursion manually with a stack, or switch to
BFS

You might also like