Table of Contents
Chapter 3.2: Graph Connectivity and Traversal
Given a graph G=(V,E) and two particular nodes s and t, how do we know if there is a path from s to t in G? If the graph is small, this problem can be solved naturally by inspection. However, for large graphs (with many nodes and edges), there are two natural algorithms for solving this problem.
Breadth-First Search
With BFS, we explore outward from s in all possible directions, adding nodes one “layer” at a time. We start with s and add all nodes that are joined by an edge to s, the first layer. Then we add all nodes that are joined by an edge to a node in the first layer, the second layer. We continue this way until there are no new nodes encountered.
- Layer L_0 consists of s
- Layer L_1 consists of all nodes that are adjacent to s
- Assuming we have defined layers L_1,…,L_j, then the layer L_j+1 consists of all nodes that do not belong to an earlier layer and that have an edge to a node in layer L_j
Note that layer L_j is the set of all nodes at distance exactly j from s. A node fails to appear in any of the layers if and only if there is no path to it. So BFS is not only determining the nodes that s can reach, it is also computing the shortest paths to them. BFS also produces a tree T rooted at s; we call this tree a breadth-first search tree.
Suppose T is a breadth-first search tree. Let x and y be nodes in T belonging to layers L_i and L_j respectively, and let (x,y) be an edge of G. Then i and j differ by at most 1.
The set of nodes discovered by the BFS algorithm is precisely those reachable from the starting node s. Call this set R the connected component of G containing s. We can also build R in a more general way. Begin by defining R = {s}. Then, if we find an edge (u,v) where u is in R but v is not in R, we can add v to R. We can keep growing the set R until there are no more edges leading out of R. The set R produced at the end of the algorithm is precisely the connected component of G containing s.
Depth-First Search
With DFS, we start from a node s and try the first edge leading out of it, to a node v. We then follow the first edge leading out of v, and continue in this way until we reach a “dead end”– a node for which we have already explored all its neighbors. Then, we backtrack until we reach a node with an unexplored neighbor, and resume from there. The DFS will also yield a rooted tree T, but it will be very different (structurally) from the T produced by BFS. We will make s the root of the tree and make u the parent of v when u is responsible for the discovery of v. The resulting tree is called a depth-first search tree. When DFS(u) is called, all nodes that are marked “explored” between the invocation and end of the recursive call are descendants of u in T.
Suppose T be a depth-first search tree. Let x and y be nodes in T and let (x,y) be an edge of G that is not an edge of T. Then one of x or y is an ancestor of the other.
NOTE: Both BFS and DFS build the connected component containing s.
For any two nodes s and t in a graph, their connected components are either identical or disjoint.