Splitting and joining binary search trees
Split a search tree at a key k into two trees (keys below and above k), then join the two again; both O(h). A split followed by a join deletes k.
SPLIT(t, k) cuts a search tree into three parts: a tree small with the keys below k, the node k (if present),
and a tree big with the keys above k. Only k's search path is walked:
- Time:
O(h) - Space:
O(h) for the recursion - Horowitz–Sahni, chapter 5
A tree of 10 keys with k = 40; the search path for 40 cuts the tree in two, then small's largest key, 35, becomes the joined tree's root.
class Node:def __init__(self, key):self.key = keyself.left = Noneself.right = Nonedef split_join(A, k):root = build(A[0])small, mid, big = split(root, k)found = mid is not Noneroot = Noneroot = join(small, big)small = Nonebig = Nonekeys = []inorder(root, keys)return keysdef split(t, k):# Returns the keys < k, the node with key k (or None), and the keys > k, as three search trees.if t is None:return None, None, Noneif k < t.key:s, m, b = split(t.left, k)t.left = breturn s, m, tif k > t.key:s, m, b = split(t.right, k)t.right = sreturn t, m, bs, b = t.left, t.rightt.left = Nonet.right = Nonereturn s, t, bdef join(small, big):# Every key in small is below every key in big. The largest key of small becomes the new root.if small is None:return bigparent = Nonem = smallwhile m.right is not None:parent = mm = m.rightif parent is None:small = m.leftelse:parent.right = m.leftm.left = smallm.right = bigreturn mdef inorder(t, keys):if t is not None:inorder(t.left, keys)keys.append(t.key)inorder(t.right, keys)def build(keys):root = Nonefor key in keys:node = Node(key)if root is None:root = nodecontinuep = rootwhile True:if key < p.key:if p.left is None:p.left = nodebreakp = p.leftelse:if p.right is None:p.right = nodebreakp = p.rightreturn root
Start
∅
∅
∅
Recursion tree
Filled orange = running call · Orange outline = still on the stack · Grey = finished
Local variables
Orange = changed in this step
A list
Call stack
- split_join()line 8
Explanation
Splitting
SPLIT(t, k) cuts a search tree into three parts: a tree small with the keys below k, the node k (if present),
and a tree big with the keys above k. Only k's search path is walked:
- if
k < t.key, nodetgoes tobigwith its right subtree, and the split goes on in the left subtree; - if
k > t.key, the other way round:tgoes tosmallwith its left subtree.
Joining
JOIN(small, big) merges two trees where every key of the first is below every key of the second: take out small's
largest node and make it the new root, with small on its left and big on its right. With a middle key m in hand
(a three-way join) it is simpler still: m becomes the root.
Cost
Both walk a single path from the root down: O(h). These two operations underlie treaps, splay trees, and sets
with union and intersection.
The input
A is the insertion order of the keys and k the key to split at.