🚧 We are still building the lighthouse. Hang tight!

projectlighthouse logoprojectlighthouse

Sign in with your Google or GitHub account to get started

Depth-First Search

DFS traversal: preorder, inorder, and postorder traversals. Understand recursive and iterative implementations.

Depth-First Search (DFS) on trees explores as deep as possible before backtracking. There are three main traversal orders: preorder, inorder, and postorder. Each visits nodes in a different sequence, suited for different problems.

The Three DFS Traversals

All three visit every node exactly once, but in different orders:

       1
      / \
     2   3
    / \
   4   5

Preorder:  1, 2, 4, 5, 3  (Node → Left → Right)
Inorder:   4, 2, 5, 1, 3  (Left → Node → Right)
Postorder: 4, 5, 2, 3, 1  (Left → Right → Node)

Preorder Traversal (Node → Left → Right)

Visit the current node before its children.

def preorder(root):
    if root is None:
        return

    print(root.val)      # Process node
    preorder(root.left)  # Visit left subtree
    preorder(root.right) # Visit right subtree

Collecting into a list:

def preorder_collect(root):
    result = []

    def traverse(node):
        if node is None:
            return

        result.append(node.val)
        traverse(node.left)
        traverse(node.right)

    traverse(root)
    return result

Use cases:

  • Copy a tree
  • Serialize a tree
  • Prefix expression (Polish notation)
  • Build a tree from description

Inorder Traversal (Left → Node → Right)

Visit left subtree, then current node, then right subtree.

def inorder(root):
    if root is None:
        return

    inorder(root.left)   # Visit left subtree
    print(root.val)      # Process node
    inorder(root.right)  # Visit right subtree

Collecting into a list:

def inorder_collect(root):
    result = []

    def traverse(node):
        if node is None:
            return

        traverse(node.left)
        result.append(node.val)
        traverse(node.right)

    traverse(root)
    return result

BST Property

Inorder traversal of a Binary Search Tree produces values in sorted order. This is a key property for validating BSTs and finding k-th smallest elements.

Use cases:

  • Get sorted values from BST
  • Validate BST
  • Expression evaluation (infix notation)

Postorder Traversal (Left → Right → Node)

Visit both subtrees before the current node.

def postorder(root):
    if root is None:
        return

    postorder(root.left)  # Visit left subtree
    postorder(root.right) # Visit right subtree
    print(root.val)       # Process node

Collecting into a list:

def postorder_collect(root):
    result = []

    def traverse(node):
        if node is None:
            return

        traverse(node.left)
        traverse(node.right)
        result.append(node.val)

    traverse(root)
    return result

Use cases:

  • Delete a tree (delete children before parent)
  • Calculate tree size or height
  • Evaluate expression trees
  • Postfix expression (Reverse Polish notation)

Visualizing Traversals

         A
        / \
       B   C
      / \   \
     D   E   F

Preorder: A B D E C F
- Visit A, then go left to B
- Visit B, go left to D
- Visit D (leaf), backtrack
- Visit E (leaf), backtrack to A
- Go right to C, visit C
- Go right to F, visit F

Inorder: D B E A C F
- Go left until leaf D
- Visit D, backtrack
- Visit B, go right to E
- Visit E, backtrack to A
- Visit A, go right to C
- Visit C, go right to F
- Visit F

Postorder: D E B F C A
- Go deep left to D
- Visit D, backtrack
- Visit E, backtrack to B
- Visit B, backtrack to A
- Go right to C, go right to F
- Visit F, backtrack
- Visit C, backtrack
- Visit A (last)

Iterative Implementations

Preorder with Stack

def preorder_iterative(root):
    if root is None:
        return []

    result = []
    stack = [root]

    while stack:
        node = stack.pop()
        result.append(node.val)

        # Push right first so left is processed first
        if node.right:
            stack.append(node.right)
        if node.left:
            stack.append(node.left)

    return result

Inorder with Stack

def inorder_iterative(root):
    result = []
    stack = []
    current = root

    while current or stack:
        # Go left as far as possible
        while current:
            stack.append(current)
            current = current.left

        # Process node
        current = stack.pop()
        result.append(current.val)

        # Move to right subtree
        current = current.right

    return result

Postorder with Stack

def postorder_iterative(root):
    if root is None:
        return []

    result = []
    stack = [root]

    while stack:
        node = stack.pop()
        result.append(node.val)

        if node.left:
            stack.append(node.left)
        if node.right:
            stack.append(node.right)

    return result[::-1]  # Reverse for postorder

Or using two stacks:

def postorder_two_stacks(root):
    if root is None:
        return []

    result = []
    stack1 = [root]
    stack2 = []

    while stack1:
        node = stack1.pop()
        stack2.append(node)

        if node.left:
            stack1.append(node.left)
        if node.right:
            stack1.append(node.right)

    while stack2:
        result.append(stack2.pop().val)

    return result

Morris Traversal (O(1) Space)

Inorder traversal without recursion or stack:

def inorder_morris(root):
    result = []
    current = root

    while current:
        if current.left is None:
            result.append(current.val)
            current = current.right
        else:
            # Find inorder predecessor
            predecessor = current.left
            while predecessor.right and predecessor.right != current:
                predecessor = predecessor.right

            if predecessor.right is None:
                # Make current the right child of predecessor
                predecessor.right = current
                current = current.left
            else:
                # Revert the change
                predecessor.right = None
                result.append(current.val)
                current = current.right

    return result

Morris Traversal Modifies Tree

Morris traversal temporarily modifies tree structure, then restores it. Don't use in multi-threaded environments or when tree structure must not change.

Common DFS Problems

Calculate Height

def height(root):
    if root is None:
        return -1  # or 0 depending on definition

    return 1 + max(height(root.left), height(root.right))

Maximum Path Sum

def max_path_sum(root):
    max_sum = float('-inf')

    def dfs(node):
        nonlocal max_sum

        if node is None:
            return 0

        left = max(0, dfs(node.left))
        right = max(0, dfs(node.right))

        # Path through this node
        max_sum = max(max_sum, left + right + node.val)

        # Return max single path
        return max(left, right) + node.val

    dfs(root)
    return max_sum

Diameter of Tree

def diameter(root):
    max_diameter = 0

    def dfs(node):
        nonlocal max_diameter

        if node is None:
            return 0

        left = dfs(node.left)
        right = dfs(node.right)

        max_diameter = max(max_diameter, left + right)

        return 1 + max(left, right)

    dfs(root)
    return max_diameter

Summary Table

| Traversal | Order | Stack Behavior | Use Cases | |-----------|-------|----------------|-----------| | Preorder | Node, L, R | Pop, push R, push L | Copy, serialize | | Inorder | L, Node, R | Go left, pop, go right | Sorted BST values | | Postorder | L, R, Node | Reverse of modified preorder | Delete tree, eval expr |

Key Takeaways

  1. DFS explores depth before breadth
  2. Preorder: node first, then children (top-down)
  3. Inorder: left, node, right (sorted order for BST)
  4. Postorder: children first, then node (bottom-up)
  5. All can be implemented recursively or iteratively
  6. Morris traversal achieves O(1) space but modifies tree
  7. Choose traversal based on problem requirements

Understanding tree DFS is essential for most tree problems. The traversal order determines when you process each node relative to its children—a crucial detail for problems like path sums, tree construction, and expression evaluation.

Notes & Highlights

© 2025 projectlighthouse. All rights reserved.