LearnAbout

Binary Trees

On this page
Quick reference
Preorder / inorder / postorder (DFS)O(n)
Level-order (BFS)O(n)
height(node)O(n)
The Idea

Every structure so far has been a straight line. An array is a row of lockers. A linked list is a scavenger hunt, one clue that leads to exactly one next clue. A stack and a queue are both a line with rules about which end you touch. A tree is the first structure that branches. A node does not point to one next thing. It can point to two.

Think of a family tree, or a company org chart. One person is at the top with no boss above them. Each person can have up to two people that report directly below them. From any one person, you can see only their own two direct reports, not the whole chart at the same time. To reach someone three levels down, you must go through the people between you and them, one hop at a time.

A binary tree is this exact shape, made formal. Each node holds a value and two pointers, left and right. Each pointer points to another node or to nothing. The one node that nobody points to is the root, the top of the chart. A node whose left and right are both empty is a leaf, the bottom of a branch, with nobody that reports to them.

A linked list node has one next. A binary tree node has two, left and right. This one change, one pointer that becomes two, is what turns a straight line into a shape that branches.
Build It
The node
structure Node:
    value
    left          // -> another Node, or NULL
    right         // -> another Node, or NULL

// The tree itself is a reference to its root node. A NULL root is an empty tree.
preorder traversal β€” root, left, right
function preorder(node, result):
    if node is NULL:                // base case: nothing here to visit
        return
    result.append(node.value)       // visit the root before either subtree
    preorder(node.left, result)
    preorder(node.right, result)
This comes directly from Recursion Basics: a base case (a null node, do nothing) and a recursive case (visit, then trust the left subtree and the right subtree to handle themselves). A tree does not change that shape. It only makes two recursive calls instead of one.
inorder traversal β€” left, root, right
function inorder(node, result):
    if node is NULL:
        return
    inorder(node.left, result)
    result.append(node.value)       // visit the root between the two subtrees
    inorder(node.right, result)
These are the same three lines, with the root moved to the middle. This order looks arbitrary on a plain binary tree. But the next topic adds one rule to this structure that makes inorder traversal come out in sorted order for free.
postorder traversal β€” left, right, root
function postorder(node, result):
    if node is NULL:
        return
    postorder(node.left, result)
    postorder(node.right, result)
    result.append(node.value)       // visit the root last, after both subtrees are done
level-order traversal (BFS) β€” one level at a time, using a queue
function levelOrder(root):
    result = []
    if root is NULL:
        return result
    queue = Queue()
    enqueue(queue, root)
    while queue is not empty:
        node = dequeue(queue)
        result.append(node.value)
        if node.left is not NULL:
            enqueue(queue, node.left)
        if node.right is not NULL:
            enqueue(queue, node.right)
    return result
This is exactly why Phase 3 taught queues. A stack would visit depth-first and give you the wrong order. When you enqueue the children of a node, they wait in line behind everyone else found at the same level. So the queue drains one full level before it starts the next level.
height β€” 1 + the taller of the two subtrees
function height(node):
    if node is NULL:                            // base case: an empty subtree has height 0
        return 0
    return 1 + max(height(node.left), height(node.right))
Know It

Every traversal touches each node exactly one time, so time is O(n) for all of them: preorder, inorder, postorder, level-order, and height are all O(n). Space is where they are different. The three DFS traversals recurse, so their space cost is the depth of the call stack: O(h), where h is the height of the tree. Remember Recursion Basics: a pending call stays in memory until it returns. BFS never recurses, but its queue can hold a full level at the same time.

OperationTimeSpaceWhy
Preorder / inorder / postorder (DFS)O(n)O(h)Every node is visited one time, which is O(n) time. The recursion stack holds one frame per level of depth that is being explored now. So its size is the height h of the tree: O(log n) if the tree is balanced, and O(n) in the worst case if it is a fully skewed chain.
Level-order (BFS)O(n)O(n)Every node is enqueued and dequeued one time, which is O(n) time. But the queue can hold a full level at the same time, and the widest level of a balanced tree can hold up to about n/2 nodes. So the queue space is O(n) in the worst case, not O(h).
height(node)O(n)O(h)This is the same shape as any DFS traversal. It visits every node one time, and the recursion never goes deeper than the height of the tree.
Break It

Empty tree

root is NULL

Every function above starts with the same check. preorder, inorder, and postorder on a NULL node return without an append. levelOrder returns an empty list before it touches a queue. height(NULL) returns 0. If you skip this check anywhere, you dereference a node that was never there.

Single node

root exists but root.left and root.right are both NULL

The root is a leaf at the same time. Both descriptions are true of the exact same node. All three DFS orders visit it one time and stop. levelOrder enqueues it, dequeues it, finds no children to enqueue, and the queue is empty on the next check. height returns 1, not 0. The base case (NULL) is 0, but a real node, even one with no children, adds one level on top of that.

Skewed tree β€” every node has only one child

each node points only left (or only right), never both

Nothing stops a binary tree from becoming the exact shape of a linked list: one long chain, n nodes deep, with no branching. height(node) then returns n instead of logβ‚‚(n). The O(h) space bound from Know It becomes O(n) in the worst case, not O(log n). This matters more than it looks. The next topic builds a structure directly on top of binary trees, where this exact shape is the main failure mode to watch for.

Mixing up the three DFS orders

you read off preorder, inorder, and postorder for the same small tree

Take the 3-node tree with root 2, left child 1, right child 3. Preorder (root, left, right) reads 2, 1, 3. Inorder (left, root, right) reads 1, 2, 3. Postorder (left, right, root) reads 1, 3, 2. All three visit the same three nodes. Only the position of the root relative to the two subtrees changes: first, middle, or last. If you write the recursive calls in the wrong order (for example, right before left), you get a valid-looking but wrong sequence with no error. It is easy to miss on a tree bigger than three nodes.

Use It
Binary Tree Inorder Traversal
Use inorder() from Build It, exactly: left, root, right.
Easy
Binary Tree Preorder Traversal
Use preorder() from Build It, exactly: root, left, right.
Easy
Binary Tree Level Order Traversal
Use levelOrder() from Build It. The only change is to group the result by level: track how many nodes are in the queue at the start of each while-loop pass.
Medium
Maximum Depth of Binary Tree
This is height() from Build It with a different name: 1 + max(depth(left), depth(right)), with the base case NULL that returns 0.
Easy
Symmetric Tree
Write a helper that compares two subtrees as mirror images: left.left against right.right, and left.right against right.left.
Easy
Invert Binary Tree
At each node, swap node.left and node.right. Then recurse into both. The base case is NULL, the same shape as every traversal above.
Easy
Balanced Binary Tree
Use height() from Build It, extended to also check |height(left) - height(right)| <= 1 at every node, not only at the root.
Easy
Diameter of Binary Tree
At every node, the longest path through it is height(left) + height(right). Compute the height bottom-up one time, and track the best sum that you see anywhere.
Easy
Same Tree
Two NULLs are the same tree. One NULL and one real node are never the same. In other cases, compare the values and recurse on both left pairs and right pairs.
Easy
Path Sum
Recurse down and subtract node.value from the target. The base case is a NULL node (fail) or a leaf where the remaining target is exactly zero (success).
Easy