Depth-First Search
Depth-first search explores one unfinished branch before backtracking. The implicit or explicit stack records the current search frontier.
def dfs(graph, start):
visited = {start}
parent = {start: None}
stack = [start]
order = []
while stack:
vertex = stack.pop()
order.append(vertex)
for neighbor in reversed(graph.get(vertex, ())):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = vertex
stack.append(neighbor)
return order, parent
Marking a vertex when it is pushed prevents duplicate frontier entries. Reversing neighbors only preserves a chosen presentation order; traversal order otherwise depends on adjacency order.
Guarantees
With an adjacency-list representation, DFS visits the reachable subgraph in time and uses visited/stack space in the worst case.
DFS produces a depth-first forest and is a basis for:
- connected components;
- topological ordering in directed acyclic graphs;
- cycle detection with the appropriate state model;
- entry/exit times, low-link algorithms, and backtracking searches.
Cycle detection is not just “a visited neighbor exists”: undirected graphs must exclude the parent edge, while directed graphs distinguish active from finished vertices.
DFS versus shortest paths
DFS can find a path, but not necessarily one with the fewest edges. Use BFS for unweighted shortest paths, or a weighted shortest-path algorithm when edges have costs.