#
Recursive Depth First Search (122A)
#
Vertex Status
Similar to whatever first search, we assign each vertex a STATUS.
NEW- literally never seen before, all vertices start with this status
ACTIVE- seen before, but the adjacent vertices are not done processing yet
FINISHED- the vertex is seen and all its children are done processing
#
Pseudocode
// main routine
function DfsSearch(G):
for each vertex v in G:
mark v as NEW
for each vertex v in G:
if v is NEW:
DfsVisit(v)
function DfsVisit(u):
mark u as ACTIVE
// process when u is ACTIVE
Preprocess(u)
for each adjacent vertex v:
if v is NEW:
DfsVisit(v)
mark u as FINISHED
// process when u is FINISHED
Postprocess(u)
// globals
time = 0
DISCOVER_TIME = {}
FINISH_TIME = {}
// main routine
function DfsSearch(G):
for each vertex v in G:
mark v as NEW
for each vertex v in G:
if v is NEW:
DfsVisit(v)
function DfsVisit(u):
mark u as ACTIVE
time++
DISCOVER_TIME[u] = time
Preprocess(u)
for each adjacent vertex v:
if v is NEW:
DfsVisit(v)
mark u as FINISHED
time++
FINISH_TIME[u] = time
Postprocess(u)
#
Python: Basic DFS
#
Comparison with stack based Whatever First Search
function WfsDepth(G: Graph):
for each vertex v in G:
mark v as NEW
for each vertex v in G:
if v is NEW:
WfsVisit(G, v)
function WfsVisit(G: Graph, start: Vertex):
stack = Stack()
stack.put(start)
while stack is not empty:
u = stack.popFirst()
if u is not VISITED:
process u
mark u as VISITED
for each adjacent vertex v:
stack.put(v)
function DfsSearch(G: Graph):
for each vertex v in G:
mark v as NEW
for each vertex v in G:
if v is NEW:
DfsVisit(v)
function DfsVisit(u: Vertex):
mark u as ACTIVE
Preprocess(u)
for each adjacent vertex v:
if v is NEW:
DfsVisit(v)
mark u as FINISHED
Postprocess(u)
The recursive version replaces stack.put(v) with the recursive call DfsVisit(v).
while stack is not empty is the same as coming back to the initial call (empty call stack).
Every time DfsVisit(v) is called in the main routine, a new call stack is initialized. WFS does this by calling the stack constructor.
#
Runtime Analysis
For a graph with V vertices and E edges, we call the DfsVisit(v) function V times in the main routine. The total number of recursive calls inside DfsVisit(v) is E times because we will traverse each edge exactly once, so E times for the entire graph.
Therefore the total runtime is O(V+E).
#
Classifying Edges and Cycle Detection
There are 4 possible types of edges:
\begin{array}{||c:cccc||}
\hline\\
\text{Tree} & &\texttt{ACTIVE}&\longrightarrow& \texttt{NEW}\\\\
\text{Back}&& \texttt{ACTIVE}&\longrightarrow& \texttt{ACTIVE}\\\\
\text{Forward}&&\texttt{ACTIVE}&\longrightarrow& \texttt{FINISHED}\\\\
\text{Cross} &&& \text{Everything else}\\\\
\hline
\end{array}More specifically:
#
Edge Interpretations
At least one back edge is found \iff the graph has a cycle. So Directed Acyclic Graphs (Like a DP dependency graph) cannot have a back edge.
Found at least 1 cross or forward edge \implies the graph is directed
Read more here
#
Python: DFS with edge classification
Right now it only finds back and tree edges correctly, still figuring out how to find forward and cross edges