Depth-first Search
Used for searching graph data structures for a node that satisfies a given property. It starts a given node (the root for a tree), and explores as far as possible along each branch before backtracking to the next. Extra memory, usually a stack, is used to keep track of child nodes not-yet explored (for backtracking).
NB: The algorithm can be applied to an implicit tree (where the next nodes are discovered while iterating).
Typically, requires less memory than Breadth-first search, but it might get stuck in a potentially infinite branch.
Algorithm
- Initialize a stack to keep track of nodes to visit, starting with the root node (or any start node).
- Mark the initial node as visited. If it’s not already marked, mark it in your data structure representing “visited” nodes.
- While the stack is not empty: Pop a node from the top of the
stack and call it
current
. - For each unvisited neighbour of
current
:- Mark this neighbour as visited.
- Push this neighbour onto the stack.
- Repeat until the stack is empty, ensuring all nodes are visited exactly once unless there’s a cycle or other constraints dictate differently.
If you need to find paths or detect cycles, consider additional data structures like parent pointers and checks during traversal (e.g. checking if a neighbour has already been visited).
Given this tree:
graph TD; a-->b & c; b-->d & e; c-->f & g; e-->h;
Iteration would be over a
, b
, d
, e
, h
, c
, f
, g
.
Non-recursive Implementation
Would traverse the graph right-to-left.
bool DFS(std::vector<node*>& graph, node* source, node* target){
std::vector<node*> frontier;
frontier.push(source);
while (!frontier.empty()) {
auto* v = frontier.pop();
if (!v->was_visited) {
v->was_visited = true;
for (auto* neighbour : v->neighbours()) {
if(neighbour == target) return true;
frontier.push(neighbour);
}
}
}
return false;
}
Recursive Implementation
Would traverse the graph left-to-right.