CSE-250 Fall 2022 - Section B - Graph Exploration

### Graph Exploration

Oct 7, 2022

#### Edge List Summary

• addEdge, addVertex: $O(1)$
• removeEdge: $O(1)$
• removeVertex: $O(m)$
• vertex.incidentEdges: $O(m)$
• vertex.edgeTo: $O(m)$
• Space Used: $O(n) + O(m)$

#### Adjacency List Summary

• addEdge, addVertex: $O(1)$
• removeEdge: $O(1)$
• vertex.incidentEdges: $O(deg(vertex))$
• removeVertex: $O(deg(vertex))$
• vertex.edgeTo: $O(deg(vertex))$
• Space Used: $O(n) + O(m)$

#### Adjacency Matrix Summary

• addEdge, removeEdge: $O(1)$
• addVertex, removeVertex: $O(n^2)$
• vertex.incidentEdges: $O(n)$
• vertex.edgeTo: $O(1)$
• Space Used: $O(n^2)$

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

#### Connectivity Problems

Given graph $G$:

• Is vertex $u$ adjacent to $v$?
• Is vertex $u$ connected to $v$ via some path?
• Which vertices are connected to vertex $v$?
• What is the shortest path from vertex $u$ to $v$?

#### 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

• Visit every vertex in graph $G = (V, E)$
• Construct a spanning tree for every connected component
• Side Effect: Compute connected components
• Side Effect: Compute a paths between all connected vertices
• Side Effect: Determine if the graph is connected
• Side Effect: Identify Cycles
• Complete in time $O(|V| + |E|)$

#### 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

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)
}
}
}
}  

#### DFS vs Mazes

The DFS algorithm is like our stack-based maze solver

• Mark each grid square with VISITED.
• Mark each path with SPANNING.
• Only visit each vertex once.
• DFS won't necessarily find the shortest paths.

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.

• If v.label == VISITED, both DFS, DFSOne skip it.

$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|)$