Author: Hayden Smith 2021
Why?
What?
Allow us to ask these kinds of questions:
There are 3 topics we will focus on relating to traversal. All rely on very similar algorithms and processes, they're just used to different extents.
Traverse for Item Searching
Using an algorithm to move between nodes to look for a value
Traverse for Path Finding
Using an algorithm to look for a value, but keep track of how to get there
Traverse Fully
Like path finding, except we're just exploring the entire graph, not looking for specific value
Graph Traversal is the systematic exploration of a graph via the edges. The focus is often between exploring paths between a starting vertex and a finish vertex.
There are two primary methods we'll be using to traverse a graph:
Both approaches ignore some edges by remembering previously visited vertices (this also prevents cycles)
Traversing with BFS is about start at a node, and expanding out equally from there. You visit all the neighbours of the current vertex, then you visit all the neighbours of those neighbours. Etc.
A very tricky algorithm to describe recursively. Typically we do it iteratively
findPathBFS(G,src,dest):
visited[] // store previously visited node
for all vertices v∈G:
visited[v]=-1
found = false
visited[src] = src
enqueue src into queue Q
while not found ∧ Q is not empty:
dequeue v from Q
if v = dest:
found = true
else:
for each (v,w) ∈ edges(G) with visited[w] = -1:
visited[w] = v
enqueue w into Q
end while
if found:
display path in dest..src order
For an adjacency list representation:
Each vertex visited at most once. Cost => O(V)
Visit all edges incident on visited vertices. Cost => O(E)
BFS: O(V + E) (adjacency list)
Traversing with DFS is about going as deep as possible until you reach a dead end, and then unwinding back through nodes until there is another branch to take.
An iterative DFS is identical to an iterative BFS, except we use a stack instead of a queue.
findPathDFS(G,src,dest):
visited[] // store previously visited node
for all vertices v ∈ G:
visited[v] = -1
found = false
visited[src] = src
push src into stack S
while not found ∧ Q is not empty:
pop stack from S
if v = dest:
found = true
else:
for each (v,w) ∈ edges(G) with visited[w] = -1:
visited[w] = v
push w into stack S
end while
if found:
display path in dest..src order
Recursive solutions are a bit more elegant.
Full Traversal
Path Checking
visited = {}
depthFirst(G,v):
visited = visited ∪ {v}
for all (v,w) ∈ edges(G):
if w ∉ visited:
depthFirst(G,w)
visited = {}
hasPath(G,src,dest):
return dfsPathCheck(G,src,dest)
dfsPathCheck(G,v,dest):
visited = visited ∪ {v}
for all (v,w) ∈ edges(G):
if w = dest: // found edge to dest
return true
else if w ∉ visited:
if dfsPathCheck(G,w,dest):
return true // found path via w to dest
return false // no path from v to dest
Path Finding
visited[] // store previously visited node
findPath(G,src,dest):
for all vertices v ∈ G:
visited[v] = -1
visited[src] = src // starting node of the path
if dfsPathCheck(G,src,dest):
// show path in dest..src order
v = dest
while v ≠ src:
print v"-"
v = visited[v]
print src
dfsPathCheck(G,v,dest):
for all (v,w) ∈ edges(G):
if visited[w] = -1:
visited[w] = v
if w = dest: // found edge from v to dest
return true
else if dfsPathCheck(G,w,dest):
return true // found path via w to dest
return false // no path from v to dest
For an adjacency list representation:
Each vertex visited at most once. Cost => O(V)
Visit all edges incident on visited vertices. Cost => O(E)
BFS: O(V + E) (adjacency list)
DFS does not guarantee optimal solution
Example below clearly does not find shortest path
A full traversal of a tree is essentially using BFS or DFS to find a spanning tree