Skip to main content

Trees & Binary Search Trees

Overviewโ€‹

A tree is a set of nodes where each node has one parent (except the root) and no cycles. That single constraint is what makes trees useful: it guarantees exactly one path between any two nodes, so "where is X" and "how do I get to X" have unique answers.

A binary search tree adds an ordering invariant on top, and that invariant is what turns a tree into a searchable structure.

Core Conceptsโ€‹

TermMeaning
RootThe single node with no parent
LeafA node with no children
HeightLongest root-to-leaf path, in edges. Determines every operation's cost
DepthDistance from the root to a given node
Binary treeEach node has at most two children
CompleteEvery level full except possibly the last, filled left to right
BalancedHeight stays O(log n) as nodes are added โ€” see Balanced Trees

Architecture / Mechanismโ€‹

The BST invariantโ€‹

For every node: everything in the left subtree is smaller, everything in the right subtree is larger.

A binary search tree rooted at 8, with 3 and 10 as its children; 3 has children 1 and 6; 6 has children 4 and 7; 10 has right child 14, which has left child 13
Every value left of 8 is below it, every value right of it is above โ€” and the same holds recursively at 3, at 6, and at every other node. Wikimedia Commons, Public domain

That invariant makes search a sequence of one-way decisions. Looking for 7: at 8 go left, at 3 go right, at 6 go right, found โ€” three comparisons instead of nine.

def search(node, key):
while node:
if key == node.value:
return node
node = node.left if key < node.value else node.right
return None

def insert(node, key):
if node is None:
return Node(key)
if key < node.value:
node.left = insert(node.left, key)
elif key > node.value:
node.right = insert(node.right, key)
return node # equal keys ignored; a real implementation decides a policy

Both are O(height). The entire question is therefore what the height is.

Deletion, and the one case that is awkwardโ€‹

Removing a node with zero or one child is a splice. Removing a node with two children cannot be โ€” neither child can take its place without violating the invariant. The fix is to replace the value with its in-order successor (the smallest value in the right subtree), then delete that successor, which by construction has at most one child:

def delete(node, key):
if node is None:
return None
if key < node.value:
node.left = delete(node.left, key)
elif key > node.value:
node.right = delete(node.right, key)
else:
if node.left is None:
return node.right
if node.right is None:
return node.left
succ = node.right # smallest value greater than node
while succ.left:
succ = succ.left
node.value = succ.value
node.right = delete(node.right, succ.value)
return node

Traversalsโ€‹

OrderVisitsProducesUsed for
In-orderleft, node, rightSorted sequence โ€” for a BSTIterating in key order
Pre-ordernode, left, rightRoot firstCopying/serialising a tree
Post-orderleft, right, nodeChildren before parentsFreeing memory, evaluating expressions
Level-orderBreadth-first by depthRow by rowPrinting, shortest path in an unweighted tree
def in_order(node):
if node:
yield from in_order(node.left)
yield node.value # sorted output for a BST
yield from in_order(node.right)

In-order traversal of a BST yielding sorted output is not a coincidence โ€” it is the invariant restated. It also gives a neat correctness check: if an in-order walk is not sorted, the tree is not a valid BST.

Edge Cases & Pitfallsโ€‹

An unbalanced BST is a linked list wearing a costume

Insert 1, 2, 3, 4, 5 into a plain BST in that order and every node becomes the right child of the previous one. Height is n, and every operation is O(n) โ€” with worse constants than an actual linked list, because each node also carries an unused pointer.

Sorted or nearly-sorted insertion order is not an unusual case; it is one of the most common ways real data arrives. This is the entire reason balanced trees exist, and why you should almost never use a hand-rolled plain BST in production code.

  • Recursive traversal is O(height) in stack space. On a degenerate tree that is O(n) frames and a possible stack overflow. Use an explicit stack for untrusted input.
  • Duplicate keys need an explicit policy โ€” reject, count, or keep a list per node. Silently dropping them (as the insert above does) is a decision, so make it deliberately.
  • validate_bst by checking each node against its children is wrong. The invariant is about entire subtrees, not immediate children; the correct check passes a (min, max) range down.

Comparisonsโ€‹

BST (unbalanced)Balanced BSTHash table
Search / insert / deleteO(log n) avg, O(n) worstO(log n) guaranteedO(1) expected
Sorted iterationYesYesNo
Range queries, min/maxYesYesNo
Worst caseDegenerateBoundedO(n)

Referencesโ€‹

  • Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms, Ch. 12 โ€” binary search trees, including the expected-height analysis for random insertion order.
  • Sedgewick & Wayne, Algorithms, 4th ed., ยง3.2 โ€” BSTs with a full implementation and empirical measurements.

Books & Videosโ€‹