--- template: templates/cse250_2022_slides.erb title: Graph Exploration date: Oct 7, 2022 textbook: Ch. 15.3 cat: graphics/16b/cat.png attribution: Based on slides by Tamassa Goodrich ---

Edge List Summary

Adjacency List Summary

Adjacency Matrix Summary

Ok, we have a graph, now what do we do with it?

Connectivity Problems

Given graph $G$:

A few more definitions...

A subgraph $S$ of a graph $G$ is a graph where...
$S$'s vertices are a subset of $G$'s vertices
$S$'s edges are a subset of $G$'s edges
A spanning subgraph of $G$...
Is a subgraph of $G$
Contains all of $G$'s vertices.

A few more definitions...

A graph is connected...
If there is a path between every pair of vertices
A connected component of $G$...
Is a maximal connected subgraph of $G$
  • "maximal" means you can't add any new vertex without breaking the property
  • Any subset of $G$'s edges that connects the subgraph is fine.

A few more definitions...

A free tree is an undirected graph $T$ such that:
There is exactly one simple path between any two nodes
  • T is connected
  • T has no cycles
A rooted tree is a directed graph $T$ such that:
One vertex of $T$ is the root
There is exactly one simple path from the root to every other vertex in the graph.
A (free/rooted) forest is a graph $F$ such that
Every connected component is a tree.

A few more definitions...

A spanning tree of a connected graph...
Is a spanning subgraph that is a tree.
Is not unique unless the graph is a tree.

Ok... back to the question: Connectivity!

The maze is a graph!

Recall

Searching the maze with a stack (Depth-First Search)
Try out every path, one at a time to the end...
... then backtrack and try another
Searching the maze with a queue (Breadth-First Search)
Try out every path in parallel...
... keep picking a path to extend by one step.

Depth-First Search

Primary Goals

Depth-First Search

DFS
Input: Graph $G$
Output: Label every edge as:
  • Spanning Edge: Part of the spanning tree
  • Back Edge: Part of a cycle
DFSOne
Input: Graph $G = (V, E)$, Start Vertex $v \in V$
Output: Label every edge in $v$'s connected component

Depth-First Search

Depth-First Search

object VertexLabel extends Enumeration
  { val UNEXPLORED, VISITED = Value }

object EdgeLabel extends Enumeration
  { val UNEXPLORED, SPANNING, BACK = Value }

def DFS(graph: Graph[VertexLabel.Value, EdgeLabel.Value])
{
  for(v <- graph.vertices) { v.setLabel(VertexLabel.UNEXPLORED) }
  for(e <- graph.edges)    { e.setLabel(EdgeLabel.UNEXPLORED) }
  for(v <- graph.vertices) { 
    if(v.label == VertexLabel.UNEXPLORED){ 
      DFSOne(graph, v)
    }
  }
}  

Depth-First Search

def DFSOne(graph: Graph[…], v: Graph[…]#Vertex)
{
  v.setLabel(VertexLabel.VISITED)

  for(e <- v.incident) { 
    if(e.label == EdgeLabel.UNEXPLORED){
      val w = e.getOpposite(v)

      if(w.label == VertexLabel.UNEXPLORED){
        e.setLabel(EdgeLabel.SPANNING)
        DFSOne(graph, w)
      } else {
        e.setLabel(EdgeLabel.BACK)
      }
    }
  }
}  

Depth-First Search

DFS vs Mazes

The DFS algorithm is like our stack-based maze solver

What's the complexity?

def DFS(graph: Graph[VertexLabel.Value, EdgeLabel.Value])
{
  for(v <- graph.vertices) { v.setLabel(VertexLabel.UNEXPLORED) }
  for(e <- graph.edges)    { e.setLabel(EdgeLabel.UNEXPLORED) }
  for(v <- graph.vertices) { 
    if(v.label == VertexLabel.UNEXPLORED){ 
      DFSOne(graph, v)
    }
  }
}  
def DFS(graph: Graph[VertexLabel.Value, EdgeLabel.Value])
{
  /* O(|V|) */
  for(e <- graph.edges)    { e.setLabel(EdgeLabel.UNEXPLORED) }
  for(v <- graph.vertices) { 
    if(v.label == VertexLabel.UNEXPLORED){ 
      DFSOne(graph, v)
    }
  }
}  
def DFS(graph: Graph[VertexLabel.Value, EdgeLabel.Value])
{
  /* O(|V|) */
  /* O(|E|) */
  for(v <- graph.vertices) { 
    if(v.label == VertexLabel.UNEXPLORED){ 
      DFSOne(graph, v)
    }
  }
}  
def DFS(graph: Graph[VertexLabel.Value, EdgeLabel.Value])
{
  /* O(|V|) */
  /* O(|E|) */
  /* O(|V|) Times */ { 
    if(v.label == VertexLabel.UNEXPLORED){ 
      DFSOne(graph, v)
    }
  }
}  
def DFS(graph: Graph[VertexLabel.Value, EdgeLabel.Value])
{
  /* O(|V|) */
  /* O(|E|) */
  /* O(|V|) Times */ { 
    if(v.label == VertexLabel.UNEXPLORED){ 
      /* ??? */
    }
  }
}  
def DFSOne(graph: Graph[…], v: Graph[…]#Vertex)
{
  v.setLabel(VertexLabel.VISITED)

  for(e <- v.incident) { 
    if(e.label == EdgeLabel.UNEXPLORED){
      val w = e.getOpposite(v)

      if(w.label == VertexLabel.UNEXPLORED){
        e.setLabel(EdgeLabel.SPANNING)
        DFSOne(graph, w)
      } else {
        e.setLabel(EdgeLabel.BACK)
      }
    }
  }
}  
def DFSOne(graph: Graph[…], v: Graph[…]#Vertex)
{
  /* O(1) */

  for(e <- v.incident) { 
    if(e.label == EdgeLabel.UNEXPLORED){
      val w = e.getOpposite(v)

      if(w.label == VertexLabel.UNEXPLORED){
        e.setLabel(EdgeLabel.SPANNING)
        DFSOne(graph, w)
      } else {
        e.setLabel(EdgeLabel.BACK)
      }
    }
  }
}  
def DFSOne(graph: Graph[…], v: Graph[…]#Vertex)
{
  /* O(1) */

  /* O(deg(v)) times */ { 
    if(e.label == EdgeLabel.UNEXPLORED){
      val w = e.getOpposite(v)

      if(w.label == VertexLabel.UNEXPLORED){
        e.setLabel(EdgeLabel.SPANNING)
        DFSOne(graph, w)
      } else {
        e.setLabel(EdgeLabel.BACK)
      }
    }
  }
}  
def DFSOne(graph: Graph[…], v: Graph[…]#Vertex)
{
  /* O(1) */

  /* O(deg(v)) times */ { 
    /* O(1) */ {
      /* O(1) */

      /* O(1) */ {
        /* O(1) */
        DFSOne(graph, w)
      } else {
        /* O(1) */
      }
    }
  }
}  
def DFSOne(graph: Graph[…], v: Graph[…]#Vertex)
{
  /* O(1) */

  /* O(deg(v)) times */ { 
    /* O(1) */ {
      /* O(1) */

      /* O(1) */ {
        /* O(1) */
        /* ??? */
      } else {
        /* O(1) */
      }
    }
  }
}  

Depth-First Search

Observation: DFSOne is called on each vertex at most once.

$O(|V|)$ calls to DFSOne

What's the runtime of DFSOne excluding recursive calls?


def DFSOne(graph: Graph[…], v: Graph[…]#Vertex)
{
  /* O(1) */

  /* O(deg(v)) times */ { 
    /* O(1) */ {
      /* O(1) */

      /* O(1) */ {
        /* O(1) */
        DFSOne(graph, w)
      } else {
        /* O(1) */
      }
    }
  }
}  

What's the runtime of DFSOne excluding recursive calls?

$$O(deg(v))$$

Depth-First Search

What's the sum over all calls to DFSOne?

$\sum_{v \in V} O(deg(v))$

$ = O(\sum_{v \in V} deg(v))$

$ = O(2 |E|)$ (by rule)

$ = O(|E|)$

Depth-First Search

Summing up...

Mark Vertices UNVISITED $O(|V|)$
Mark Edges UNVISITED $O(|E|)$
DFS Vertex Loop $O(|V|)$
All Calls to DFSOne $O(|E|)$
$O(|V| + |E|)$