Binary Search Trees
Week 9, Monday
March 2, 2026
d-ary Tree Definition
A d-ary tree T is either:
- _______________________________________
OR
- _______________________________________
Binary Tree Height
height(r) – length of longest path from node r to a leaf
Given a binary tree T, height(T) is
_______________________________________
_______________________________________
Height and Node Count
Question: What’s the maximum number of nodes, \(N(h)\), for a binary tree of height \(h\)?
Question: What’s the least height for a tree of \(n\) nodes?
Why Height Matters
Many tree operations take time proportional to height:
- Finding a node
- Inserting a node
- Deleting a node
If height is \(O(\log n)\): these are fast (\(O(\log n)\))
If height is \(O(n)\): these are slow (\(O(n)\))
Goal: Keep trees short!!
Back to Wordle
Recall our decision tree…
- Height: worst-case # of guesses
- Goal: minimize the height
decision trees model any sequence of choices where each choice narrows down possibilities.
BUT…
The Dictionary Problem
We want to store key-value pairs with fast operations:
insert(key, value): Add a new entry
find(key): Look up a value by key
remove(key): Delete an entry
Python’s dict does this in O(1) expected time using hashing.
Can we do it with a tree?
Problem: In an arbitrary tree, where should a key go?
If I want to find 25, where do I look? If I want to insert 37, where should I put it?
What Do You Notice?
The Binary Search Tree Property
A Binary Search Tree (BST) is a binary tree where, for every node \(r\):
- \(x \in r.\text{left} \Rightarrow x.\text{key} < r.\text{key}\)
- \(x \in r.\text{right} \Rightarrow x.\text{key} > r.\text{key}\)
- \(r.\text{left}\) is a BST
- \(r.\text{right}\) is a BST
Implementation
from dataclasses import dataclass
@dataclass
class BSTNode:
key: int
# value: valueType
left: 'BSTNode' = None
right: 'BSTNode' = None
Each node stores a key and left/right children.
BST Class: Find
@dataclass
class BST:
root: 'BSTNode' = None
def find(self, key: int) -> BSTNode:
return self._find(self.root, key)
def _find(self, node: BSTNode, key: int) -> BSTNode:
if (node is None) or (key == node.key):
return node
elif key < node.key:
return self._find(node.left, key)
else:
return self._find(node.right, key)
Running time: O(_____)
Why the Helper Function?
find(key) is the public interface — users call tree.find(25)
_find(node, key) is the recursive helper — it needs to know where in the tree we are
def find(self, key):
return self._find(self.root, key) # Start at root
The helper takes a node parameter so it can recurse on subtrees.
This pattern is common for recursive solutions on data structures.
BST Class: Insert
def insert(self, key):
self.root = self._insert(self.root, key)
def _insert(self, node, key):
if node is None: # we can insert here!!
return BSTNode(key)
if key < node.key:
node.left = self._insert(node.left, key)
elif key > node.key:
node.right = self._insert(node.right, key)
# If key == node.key, it's a duplicate; do nothing
return node
Running time: O(height)
Building a BST
Draw the resulting tree.
tree = BST()
for key in [38, 13, 51, 10, 25, 40, 84]:
tree.insert(key)
Summary: Binary Search Trees
- BST property: left < root < right (recursively)
- Find: Binary search down the tree
- Insert: Find where it belongs, add as leaf
- Running time: O(height)
- Best case: balanced tree → O(log n)
- Worst case: degenerate tree → O(n)
What’s Next
Tuesday: BST Removal
- Deleting a key is trickier than inserting
- Three cases: leaf, one child, two children
- The “inorder predecessor” strategy
Wednesday: Balanced Trees
- How do we keep a BST balanced?
- AVL trees and the guarantee of O(log n) height
Practice Problems
- Draw the BST that results from inserting: 5, 3, 7, 2, 4, 6, 8
- Draw the BST that results from inserting: 2, 3, 4, 5, 6, 7, 8
- What is the inorder traversal of each tree?
- Which tree is more efficient for searches? Why?