Splay tree
A search tree with no balance information: every key that is accessed is brought to the root by rotations, two levels at a time. Each operation costs O(log n) amortized.
A splay tree (Sleator and Tarjan) keeps no balance information. Instead, after every access the key is splayed to the root, two levels at a time:
Read the full explanation ↓- Time:
O(log n) amortized - Space:
O(h) for the recursion - Horowitz–Sahni, chapter 10
A left chain of seven (depth 7). Inserting 0 brings the deepest node to the root with zig-zig rotations, and the tree's depth roughly halves.
class Node:def __init__(self, key):self.key = keyself.left = Noneself.right = Nonedef splay_tree(root, ops):for op, key in ops:if op == "insert":root = insert(root, key)else:root = delete(root, key)return rootdef splay(root, key):# Brings key (or the last node on its search path) to the root, two levels at a time.if root is None or root.key == key:return rootif key < root.key:if root.left is None:return rootif key < root.left.key:kind = "zig-zig"root.left.left = splay(root.left.left, key)root = rotate_right(root)elif key > root.left.key:kind = "zig-zag"root.left.right = splay(root.left.right, key)if root.left.right is not None:root.left = rotate_left(root.left)if root.left is None:return rootreturn rotate_right(root)if root.right is None:return rootif key > root.right.key:kind = "zig-zig"root.right.right = splay(root.right.right, key)root = rotate_left(root)elif key < root.right.key:kind = "zig-zag"root.right.left = splay(root.right.left, key)if root.right.left is not None:root.right = rotate_right(root.right)if root.right is None:return rootreturn rotate_left(root)def rotate_right(y):x = y.lefty.left = x.rightx.right = yreturn xdef rotate_left(x):y = x.rightx.right = y.lefty.left = xreturn ydef insert(root, key):root = splay(root, key)if root is not None and root.key == key:return rootnode = Node(key)if root is None:return nodeif key < root.key:node.left = root.leftnode.right = rootroot.left = Noneelse:node.right = root.rightnode.left = rootroot.right = Nonereturn nodedef delete(root, key):root = splay(root, key)if root is None or root.key != key:return rootif root.left is None:return root.rightright = root.rightroot = splay(root.left, key)root.right = rightreturn root
Start
Recursion tree
Filled orange = running call · Orange outline = still on the stack · Grey = finished
Local variables
Orange = changed in this step
root object
- key = 7
- left = Node(key=6, left=Node(key=5, left=Node(key=4, left=…, right=None), right=None), right=None)
- right = None
ops list
Call stack
- splay_tree()line 8
Explanation
The idea
A splay tree (Sleator and Tarjan) keeps no balance information. Instead, after every access the key is splayed to the root, two levels at a time:
- zig-zig: the key and its parent are both left (or both right) children: rotate the grandparent first, then the parent;
- zig-zag: one left, one right: rotate the parent first, then the grandparent;
- zig: one level left: a single rotation.
Why it works
One operation can take O(n), but the amortized cost of each is O(log n) (with a potential equal to the sum of
the logs of the subtree sizes). A zig-zig roughly halves the depth of the path, which is what sets splaying apart from
simply rotating a node up to the root. Popular keys stay near the root.
Insertion and deletion
- Insert: splay
k's place to the top;kbecomes the root and the old tree is split in two under it. - Delete: splay
kto the root, cut it out, and splay the left subtree's largest key to its top, so the right subtree can hang from it.
The input
The keys first build an ordinary search tree (possibly unbalanced), then the operations run.