Trees & Binary Trees Made Easy

Learn step-by-step with real examples and visual explanations

Medium 35 min read Interactive

Why Trees Matter in Real Life

Trees are everywhere because they naturally represent hierarchical relationships

Anything with a parent-child or belongs-to relationship is a tree!

File Systems

Every folder and file on your computer is organized as a tree! The root is C:\ or /, folders are internal nodes, files are leaves.

HTML/DOM

Every webpage is a tree! The <html> tag is the root, and all other elements are its descendants.

Game AI

Decision trees help game AI choose the best move. Each node represents a game state, edges are possible moves.

Databases

B-trees and B+ trees make database searches lightning fast, even with millions of records!

Auto-complete

When you type in Google, a special tree called a Trie suggests completions instantly!

Organization Charts

Company hierarchies are trees! CEO at the root, managers as internal nodes, employees as leaves.

Understanding Trees - The Basics

Real-World Analogy: Your Family Tree

Think of a tree like your family tree:

👴
Grandparents
Ancestors (Root)
👨‍👩
Parents
Parent Nodes
🧑
You
Current Node
👶
Children
Child Nodes

Just like how you can trace your family lineage, in a tree data structure, we can trace paths from one node to another!

What Makes a Tree?

A tree is a collection of nodes connected by edges, with these simple rules:

Try It Yourself: Build Your First Tree!

Interactive Tree Builder

Tree Vocabulary (Made Simple!)

Node

A single element in the tree that stores data

Edge

The connection between two nodes (parent-child link)

Root

The topmost node with no parent (the boss!)

Leaf

A node with no children (end of a branch)

Height

The longest path from root to any leaf

Depth

Distance from root to a specific node

Your First Tree Node in Python

tree_node.py
# A tree node is just a box with data and pointers!
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val        # The data we store
        self.left = left      # Pointer to left child
        self.right = right    # Pointer to right child

# Creating a simple tree:
#       1
#      / \
#     2   3

root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)

print(f"Root value: {root.val}")           # Output: 1
print(f"Left child: {root.left.val}")      # Output: 2
print(f"Right child: {root.right.val}")    # Output: 3

Common Beginner Mistake

Forgetting to check if a node exists before accessing it! Always check:

null_check.py
# ✘ Wrong way:
print(root.left.val)  # Crashes if left is None!

# ✔ Right way:
if root.left:
    print(root.left.val)

Tree Traversal Patterns

Think of Tree Traversal Like Exploring a House

Imagine you're exploring a house with multiple rooms and doors:

  • Preorder: Note the room, then explore left door, then right door
  • Inorder: Explore left door, note the room, then explore right door
  • Postorder: Explore both doors first, then note the room
  • Level-order: Explore all rooms on floor 1, then floor 2, then floor 3...

1. Inorder Traversal (Left - Root - Right)

Perfect for BSTs - gives you sorted order!

inorder.py
def inorder(root):
    if not root:
        return []

    result = []

    # Recursive approach - clean and simple!
    def traverse(node):
        if node:
            traverse(node.left)       # Go left
            result.append(node.val)    # Process node
            traverse(node.right)       # Go right

    traverse(root)
    return result

# For tree:    4
#            /   \
#           2     6
#          / \   / \
#         1   3 5   7
# Result: [1, 2, 3, 4, 5, 6, 7] - Sorted!

2. Preorder Traversal (Root - Left - Right)

Great for copying trees - process parent before children!

preorder.py
def preorder(root):
    if not root:
        return []

    result = []

    def traverse(node):
        if node:
            result.append(node.val)    # Process node first
            traverse(node.left)        # Then go left
            traverse(node.right)       # Then go right

    traverse(root)
    return result

# Same tree, Result: [4, 2, 1, 3, 6, 5, 7]

3. Level-Order Traversal (BFS)

Process tree level by level - like reading a book!

level_order.py
from collections import deque

def levelOrder(root):
    if not root:
        return []

    result = []
    queue = deque([root])

    while queue:
        level_size = len(queue)
        current_level = []

        for _ in range(level_size):
            node = queue.popleft()
            current_level.append(node.val)

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

        result.append(current_level)

    return result

# Result: [[4], [2, 6], [1, 3, 5, 7]]

Interactive Traversal Visualizer

Traversal Demo

Practice Problems

Easy Count the Total Number of Nodes

Given a binary tree, return the total number of nodes.

Think recursively: count 1 (current) + all nodes in left subtree + all nodes in right subtree.

def countNodes(root):
    """Count total nodes in the tree"""
    if not root:
        return 0

    # Count: 1 (current) + left subtree + right subtree
    return 1 + countNodes(root.left) + countNodes(root.right)

# Even simpler to understand:
def countNodesVerbose(root):
    if not root:
        return 0

    left_count = countNodes(root.left)   # Count left side
    right_count = countNodes(root.right) # Count right side

    return 1 + left_count + right_count  # Add them up!

Easy Find the Height of a Tree

Return the height (longest path from root to any leaf) of a binary tree.

Height = 1 + max(height of left subtree, height of right subtree). Base case: empty tree has height 0.

def maxDepth(root):
    if not root:
        return 0
    return 1 + max(maxDepth(root.left), maxDepth(root.right))

Easy Count Leaf Nodes

Count how many leaf nodes (nodes with no children) exist in the tree.

A leaf node has no left child and no right child.

def countLeaves(root):
    if not root:
        return 0
    if not root.left and not root.right:
        return 1
    return countLeaves(root.left) + countLeaves(root.right)

Medium Invert a Binary Tree

Flip the tree like a mirror image so that every left child becomes a right child and vice versa.

At each node, swap left and right children, then recursively invert both subtrees.

def invertTree(root):
    """Flip the tree like a mirror image"""
    if not root:
        return None

    # Swap left and right children
    root.left, root.right = root.right, root.left

    # Recursively invert subtrees
    invertTree(root.left)
    invertTree(root.right)

    return root

# Before:  1        After:   1
#         / \               / \
#        2   3             3   2

Medium Check if Two Trees are Identical

Given two binary trees, determine if they are structurally identical and have the same values.

Two trees are identical if their roots are equal, left subtrees are identical, and right subtrees are identical.

def isSameTree(p, q):
    if not p and not q:
        return True
    if not p or not q:
        return False
    return (p.val == q.val and
            isSameTree(p.left, q.left) and
            isSameTree(p.right, q.right))

Hard Lowest Common Ancestor (LCA)

Find the lowest common ancestor of two nodes in a binary tree.

If both nodes are found in different subtrees, the current node is the LCA. If both are in the same subtree, recurse into that subtree.

def lowestCommonAncestor(root, p, q):
    if not root or root == p or root == q:
        return root

    left = lowestCommonAncestor(root.left, p, q)
    right = lowestCommonAncestor(root.right, p, q)

    if left and right:
        return root
    return left if left else right

Pro Tip

Start with the beginner problems and work your way up. Most tree problems follow similar patterns - once you understand recursion with trees, you can solve 80% of tree problems!

Binary Search Trees (BST)

Like a Dictionary!

A BST is like a dictionary where words before 'M' go to the left, words after 'M' go to the right. This makes finding any word super fast!

BST Property

For every node in a BST:

Complete BST Implementation

bst.py
class BST:
    def __init__(self):
        self.root = None

    def insert(self, val):
        """Insert a value into the BST"""
        if not self.root:
            self.root = TreeNode(val)
            return

        current = self.root
        while True:
            if val < current.val:
                if not current.left:
                    current.left = TreeNode(val)
                    break
                current = current.left
            else:
                if not current.right:
                    current.right = TreeNode(val)
                    break
                current = current.right

    def search(self, val):
        """Search for a value - O(log n) average!"""
        current = self.root
        while current:
            if val == current.val:
                return True
            elif val < current.val:
                current = current.left
            else:
                current = current.right
        return False

# Using the BST
bst = BST()
for val in [5, 3, 7, 1, 9]:
    bst.insert(val)

print(bst.search(7))  # True - found it!
print(bst.search(6))  # False - not there

Validating a BST

validate_bst.py
def isValidBST(root):
    """Check if a tree is a valid BST"""
    def validate(node, min_val, max_val):
        if not node:
            return True

        # Check if current node violates BST property
        if node.val <= min_val or node.val >= max_val:
            return False

        # Recursively validate subtrees with updated bounds
        return (validate(node.left, min_val, node.val) and
                validate(node.right, node.val, max_val))

    return validate(root, float('-inf'), float('inf'))

Common BST Mistake

Checking only immediate children isn't enough! You must ensure ALL nodes in left subtree are smaller, not just the immediate left child.

Quick Reference Guide

Time Complexity Cheat Sheet

Operation Average Case Worst Case When to Use
BST Search O(log n) O(n) When data is sortable
BST Insert O(log n) O(n) Maintaining sorted data
Tree Traversal O(n) O(n) Processing all nodes
Find Height O(n) O(n) Tree metrics
Level Order O(n) O(n) Level-wise processing

When to Use Each Traversal

Traversal Order Best For
Inorder Left - Root - Right Getting sorted values from BST
Preorder Root - Left - Right Copying/serializing trees
Postorder Left - Right - Root Deleting trees, calculating sizes
Level-order Level by level Finding minimum depth, printing levels

Essential Tree Patterns

Recursion Pattern

def treePattern(root):
    if not root:  # Base case
        return something

    # Process current
    # Recurse left
    # Recurse right
    return result

Level-order Pattern

queue = deque([root])
while queue:
    size = len(queue)
    for _ in range(size):
        node = queue.popleft()
        # Process node
        # Add children

Remember

Trees are recursive structures - most tree problems have elegant recursive solutions. Think: "What should I do at this node?" and "What should my children do?"