AlgoVerse

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 ↓
Input:
n=7 · insert 0PDF export

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.

  1. class Node:
  2. def __init__(self, key):
  3. self.key = key
  4. self.left = None
  5. self.right = None
  6. def splay_tree(root, ops):
  7. for op, key in ops:
  8. if op == "insert":
  9. root = insert(root, key)
  10. else:
  11. root = delete(root, key)
  12. return root
  13. def splay(root, key):
  14. # Brings key (or the last node on its search path) to the root, two levels at a time.
  15. if root is None or root.key == key:
  16. return root
  17. if key < root.key:
  18. if root.left is None:
  19. return root
  20. if key < root.left.key:
  21. kind = "zig-zig"
  22. root.left.left = splay(root.left.left, key)
  23. root = rotate_right(root)
  24. elif key > root.left.key:
  25. kind = "zig-zag"
  26. root.left.right = splay(root.left.right, key)
  27. if root.left.right is not None:
  28. root.left = rotate_left(root.left)
  29. if root.left is None:
  30. return root
  31. return rotate_right(root)
  32. if root.right is None:
  33. return root
  34. if key > root.right.key:
  35. kind = "zig-zig"
  36. root.right.right = splay(root.right.right, key)
  37. root = rotate_left(root)
  38. elif key < root.right.key:
  39. kind = "zig-zag"
  40. root.right.left = splay(root.right.left, key)
  41. if root.right.left is not None:
  42. root.right = rotate_right(root.right)
  43. if root.right is None:
  44. return root
  45. return rotate_left(root)
  46. def rotate_right(y):
  47. x = y.left
  48. y.left = x.right
  49. x.right = y
  50. return x
  51. def rotate_left(x):
  52. y = x.right
  53. x.right = y.left
  54. y.left = x
  55. return y
  56. def insert(root, key):
  57. root = splay(root, key)
  58. if root is not None and root.key == key:
  59. return root
  60. node = Node(key)
  61. if root is None:
  62. return node
  63. if key < root.key:
  64. node.left = root.left
  65. node.right = root
  66. root.left = None
  67. else:
  68. node.right = root.right
  69. node.left = root
  70. root.right = None
  71. return node
  72. def delete(root, key):
  73. root = splay(root, key)
  74. if root is None or root.key != key:
  75. return root
  76. if root.left is None:
  77. return root.right
  78. right = root.right
  79. root = splay(root.left, key)
  80. root.right = right
  81. return root

Invariant: The tree is always a binary search tree; after each operation the key just accessed (or its neighbour) is at the root.

Start

1234567

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

('insert', 0)

Call stack

  1. splay_tree()line 8
Step 1 of 85
Black = inserted, deleted · Orange = key stepcase: 0/3 · rotate_right: 0/6 · new_root: 0/1 · inserted: 0/1
    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; k becomes the root and the old tree is split in two under it.
    • Delete: splay k to 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.