The BST Rule, Search and Insert
28 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)A list can be searched fast only if it is sorted, and a sorted list is slow to insert into. A binary search tree keeps keys in order in a different way: every comparison throws away a whole side, and adding a key does not move the others.
What a binary search tree is
Binary search tree (BST) is a tree where every key in a node's left subtree is smaller than the node's key and every key in its right subtree is larger. This course uses distinct keys.
A tree stores items in nodes. Each node holds a key and points to at most two children, a left one and a right one. One rule about the keys makes lookups fast.
class Node: def __init__(self, key): self.key = key self.left = None self.right = NoneNone means there is no child on that side. A subtree is a node together with everything below it.
Search: pick a side at every node
To find 7, start at the root 8. 7 is smaller, so it can only be in the left subtree. Move there and repeat. Each step drops a whole side, like binary search on a list.
def search(node, key): while node is not None and node.key != key: node = node.left if key < node.key else node.right return node is not NoneExample 1 · Follow the search path
class Node: def __init__(self, key): self.key = key self.left = None self.right = None
def insert(node, key): if node is None: return Node(key) if key < node.key: node.left = insert(node.left, key) elif key > node.key: node.right = insert(node.right, key) return node
root = Nonefor key in [8, 3, 10, 1, 6, 14, 4, 7, 13]: root = insert(root, key)node = rootpath = []while node is not None: path.append(node.key) if node.key == 7: break node = node.left if 7 < node.key else node.rightprint(path)[8, 3, 6, 7]
What this code does
- The tree was built by inserting 8, 3, 10, 1, 6, 14, 4, 7, 13 in that order.
- Only four of the nine nodes are visited. The other five were never touched.
Example 2 · A key that is not there
class Node: def __init__(self, key): self.key = key self.left = None self.right = None
def insert(node, key): if node is None: return Node(key) if key < node.key: node.left = insert(node.left, key) elif key > node.key: node.right = insert(node.right, key) return node
def search(node, key): while node is not None and node.key != key: node = node.left if key < node.key else node.right return node is not None
root = Nonefor key in [8, 3, 10, 1, 6, 14, 4, 7, 13]: root = insert(root, key)print(search(root, 5))print(search(root, 13))print(search(None, 1))False True False
What this code does
- 5 would sit under 4 on the right. That child is
None, so the loop stops and returns False. - An empty tree is just
None, and search handles it with no special case.
The loop ends when the key is found or a child is None. It visits one node per level, so the time is O(h) for height h, and the space is O(1).
Insert: search, then attach
To add a key, follow the search path until a child is missing and put the new node there. Old nodes never move. A key that is already present is ignored.
def insert(node, key): if node is None: return Node(key) if key < node.key: node.left = insert(node.left, key) elif key > node.key: node.right = insert(node.right, key) return nodeExample 3 · Where a new key attaches
class Node: def __init__(self, key): self.key = key self.left = None self.right = None
def insert(node, key): if node is None: return Node(key) if key < node.key: node.left = insert(node.left, key) elif key > node.key: node.right = insert(node.right, key) return node
root = Nonefor key in [8, 3, 10, 1, 6, 14, 4, 7, 13]: root = insert(root, key)node = rootpath = []while True: path.append(node.key) child = node.left if 5 < node.key else node.right if child is None: break node = childprint(path)root = insert(root, 5)print(node.right.key)[8, 3, 6, 4] 5
What this code does
- The search for 5 ends at 4, which has no right child. Insert hangs 5 there.
- The same path decides both the search and the insert.
node.left = insert(node.left, key) returns the child, new or old, so the parent always points to the right node. Write root = insert(root, key), because the root changes when the tree is empty.
The height decides the cost
Height of a tree is the number of nodes on the longest path from the root to a leaf. An empty tree has height 0. Search and insert in a BST take O(h) time.
Height is the number of nodes on the longest path from the root down. Search and insert take O(h) steps. The same keys can make a short tree or a long chain, depending on the order they arrive in.
def height(node): if node is None: return 0 return 1 + max(height(node.left), height(node.right))Example 4 · Same keys, different shape
class Node: def __init__(self, key): self.key = key self.left = None self.right = None
def insert(node, key): if node is None: return Node(key) if key < node.key: node.left = insert(node.left, key) elif key > node.key: node.right = insert(node.right, key) return node
def height(node): if node is None: return 0 return 1 + max(height(node.left), height(node.right))
balanced = Nonefor key in [4, 2, 6, 1, 3, 5, 7]: balanced = insert(balanced, key)chain = Nonefor key in [1, 2, 3, 4, 5, 6, 7]: chain = insert(chain, key)print(height(balanced))print(height(chain))3 7
What this code does
- Inserting the middle key first splits the keys evenly, so the height is 3.
- Inserting sorted keys makes every new key a right child, so the tree is a chain of height 7 and search is no better than a list scan.
An empty tree has height 0. A single node has height 1. Recursion adds one for the current node to the taller child.
Next step
Check your understanding
12 quick questions