Graph Search Techniques

    I. Introduction

    II. Tree Traversal Techniques

    III. Depth-First Search

    IV. Breadth-First Search

    V. Biconnectivity: A Major Application of DFS

    I. Introduction

    II. Tree Traversal Techniques

    III. Depth-First Search

    IV. Breadth-First Search

  1. Example in class
    
    
    
    
    
    
    
    
    
    
    
    
    

  2. Implementation

    • Observations:
      the first node visited in each level is the first node from which to proceed to visit new nodes.

    • This suggests that a queue is the proper data structure to remember the order of the steps.

  3. Repeat the same example, but this time using a stack
    
    
    
    
    
    
    
    
    
    
    
    
    

  4. Algorithm

    
    
    Procedure BFS(input: graph G) begin Queue Q; Integer s,x; while (G has an unvisited node) do s := an unvisited node; visit(s); Enqueue(s,Q); While (Q is not empty) do x := Dequeue(Q); For (unvisited neighbor y of x) do visit(y); Enqueue(y,Q); endfor endwhile endwhile end

  5. Time Complexity:

    • Every node is visited onece. Also, every edge (x,y) is "crossed" once when node y is checked from x to see if it is visited (if not visited, then y would be visited from x).

    • Therefore, the time of BFS is O(n+|E|).
  6. Applications of BFS:
    Connectivity and spanning trees are easily done with BFS much as with DFS. The details are left as an exercise.

    V. Biconnectivity: A Major Application of DFS

    • Definition: A node in a connected graph is called an articulation point if the deletion of that node disconnects the graph.

    • Definition: A connected graph is called biconnected if it has no articulation points. That is, the deletion of any single node leaves the graph connected.

      LI> In the case of networks, an articulation point is referred to as a single point of failure.

    • The Biconnectivity Problem:

      • Input: a connected graph G

      • Problem: Determine whether or not the graph is biconncted. If not biconnected, find all the articulation points.

    • DFS on a connected graph G yields a DFS tree whose edges are from the graph. Draw those edges as straingt edges. Add the remaining edges of the graph as dashed edges in the tree.

    • Theorem: Each dashed edge goes from an descendent to an ancestor. For that reason, the dashed edges are called backward edges (or simply back edges).

    • Proof: The proof is by contradiction.

      Let (x,y) be a dashed edge between nodes that are not ancestor-descendent, that is, x and y are in separate subtrees of the DFS tree. Assume x was visited before y.

      At the very last time (say time t) when a search for unvisited neighbors of x was conducted and none found, x was backtracked from, never to return to x again.

      Well, since y is not a descendent of x, y is not visited at time t.

      But since y is a neighbor of x and y is not visited at time t, y would have to be visited from x before the algorithm backtracks from x. That would make y a descendent of x. Contradiction.

      Therefore, no such cross edge (x,y) can exist in a DFS tree. Q.E.D.

    • Observations:

      • the DFS tree along with the back edges form a new layout of the entire graph. In other terms, we don't have to refer to the origfinal layout of G anymore.

      • If the root has more than one child, then the root is an articulation point. That is because the removal of the root makes the subtrees of that root disconnected from one another since there are no cross dashed edges between them.

      • On the other hand, if the root has a single child, removing the root leaves a single tree in place, and thus the remaining graph is still connected.

      • This suggests a first algorithm for identifying articulation points: Do a DFS from each node, and check that node to see if it has more than one child. This algorithm, however, takes O(n|E|) time. A better algorithm will be designed that takes only O(|E|) time.

    • The nodes of the graph will be relabeled so that the new labels carry meaningful information. Indeed, each node i will have two new labels: DFN[i] and L[i].

    • DFN[i] is the time at which i is visited. Thus, the first node visited (i.e., the root) has its DFN = 1. The second node visited has a DFN = 2, and so on.

    • To define L[i], we need to define the notion of a special path.

    • A special path from a node x is a path that goes down from x zero or more tree edges, and then follows a single backward edge. Also, a nil path from a node to itself is called special.

    • This definition of special paths is motivated by the very important observation that a non-root node x is an articulation point if and only if x has a subtree from which no backward edge originates and ends at a proper ancestor of x.

    • The above observation can be proved by contradiction. If from every subtree of x there is a backward edge that goes to a proper ancestor of x, then the removal of x (and its incident edges) leaves the subtrees of x connected to the remainder of the tree by those backward edges. That is, the graph remains connected, implying that x is not an articulation point.

    • The latest observation can be re-stated as follows:
      x is an articulation point if and only if there exists an child w of x such that every special path from w ends up at x or below x.

    • This is equivalent to saying that x is an articulation point if and only if there exists an child w of x such that the highest node reachable from w by a special path is x or lower than x.

    • This last re-statement of the condition is a geometrical statement (involving notions of geometrical "high" and "low" positions).

    • To make the geometric condition algorithmically testable, we will convert it into an equivalent algebraic condition.

    • Clearly, if a node y is a lower descendent of a node x, then DFN[y] > DFN[x].

    • It remains to "quantify" the notion of the highest reachable node by a special path from w. The DFN of such a node will be referred to as L[w]. Since highest (gemetrically) means smallest DFN (algebraically), we can formally quantify L[w] as follows:

    • L[w]=min{DFN[y] | y is reachable from w by a special path}

    • The geometric condition translates then to
      x is an articulation point if and only if x has a child w such that L[w] >= DFN[x]

    • The DFNs are easy to compute using DFN values. It remains to develop a method to compute the L values.

    • Notice that
      L[x]=min{
      DFN[x],
      {DFN[y] | (x,y) is a back edge},
      {DFN[y] | y is reachable from some child w of x by a special path} for each child w of x}
      }

    • Since the minimum of the whole is the minimum of the minimums of the parts, we conclude that

    • L[x]=min{
      DFN[x],
      {DFN[y] | (x,y) is a back edge},
      {min{DFN[y] | y is reachable from some child w of x by a special path} for each child w of x}
      }

    • This implies that
      L[x]=min{
      DFN[x], {DFN[y] | (x,y) is a back edge},
      {min{L[w] | for each child w of x}
      }

    • The latter definition allows for a bottom computation of L[x] using DFS. Once the Ls of the children have been computed, L[x] can easily be derived.

    • The computation of L[x], being a minimum, will be carried out in progression: we initialize L[x] to DFN[x], and thereafter, everytime a relevant term (to be minimized over) becomes available, the value of L[x] is updated by comparing it to that term and assigning the term to L[x] if the term is smaller.

    • The Algorithm for finding the articulation points is therefore based on DFS. The algorithm is presented next, as a modification of the DFS algorithm. The changes are put in green color.

      
      
      Procedure DFS(input: graph G,) begin Stack S; Integer s,x; Integer DFN[1:n], L[1:n], Parent[1:n]; Integer num := 1; s := an unvisited node; visit(s); push(s,S); DFN[s] := num; num++; L[v] := DFN[v]; While (S is not empty) do x := top(S); if (x has an unvisited neighbor y) then visit(y); push(y,S); DFN[y] := num; num++; Parent[y] := x; L[y] := DFN[y]; else pop(S); for (every neighbor y of x) do if (y != parent[x] and DFN[y] < DFN[x]) then /* y is an ancestor of x, and (x,y) is a back edge*/ L[x] := min(L[x],DFN[y]); else if (x = Parent[y]) then L[x] : min(L[x],L[y]); if (L[y] >= DFN[x] and x is not root) then x is an articulation point; endif endif endif endfor endif endwhile if (s has more than one child) then s is an articulation point; endif end

    • Time Complexity:

      The new statements add constant-time operations except for the new for loop at the time of backtracking.

      This new for-loop crosses the edges one more time to update the L values and check for articulation points. This increases the time by another O(|E|).

      The final if-statement to check for the status of the root can be done by scanning the Parent array to count the number of children of the root s ( by counting the number of nodes whose Parent is s). This takes O(n) = O(|E|) time.

      Therefore, the time complexity of the whole algorithm is O(|E|).