← Wiki Index / Computation & Algorithms / Splay Tree
💻 Computation & Algorithms

Splay Tree

A splay tree is a self-adjusting binary search tree invented by Sleator and Tarjan that moves accessed nodes to the root via zig-zig and zig-zag rotations, achieving O(log n) amortized complexity and static optimality.

Splay tree is a self-adjusting binary search tree within computation and algorithms that performs searches, insertions, deletions, splits, and joins in O(log n) amortized time without storing any explicit balance metadata (such as heights, colors, or random priorities) in tree nodes. Invented by Daniel Dominic Sleator and Robert Endre Tarjan in 1985, a splay tree restructures itself on every operation via a sequence of double tree rotations called splaying, which moves the accessed node x all the way to the root while approximately halving the depth of every node along the access path.

Whereas strict worst-case balanced trees such as AVL trees and red–black trees maintain uniform O(log n) height invariants regardless of access skewedness, a splay tree adapts dynamically to skewed and temporally localized query distributions. Using the banker's potential method of amortized analysis with a logarithmic subtree-weight potential function, Sleator and Tarjan proved the Access Lemma, which simultaneously implies O(log n) amortized cost per operation, the Static Optimality Theorem (matching the asymptotic cost of an optimal static Huffman coding search tree), the Static Finger Theorem, and the Working-Set Theorem. Splay trees also serve as the auxiliary path data structure inside Sleator and Tarjan's link–cut trees for dynamic graph connectivity and network flow.

The Splaying Operation: Zig, Zig-Zig, and Zig-Zag Steps

Why Naive Single Rotations Fail

A natural heuristic for a self-adjusting binary search tree is move-to-root (Allen & Munro, 1978), which repeatedly performs single tree rotations between the accessed node x and its parent p until x reaches the root. However, single-rotation move-to-root fails to achieve O(log n) amortized performance: on a degenerate chain of n nodes, repeatedly accessing the deepest leaf rotates it to the root while pushing every other node down by one level, producing a cyclic sequence of chains where every access requires Θ(n) time.

Sleator and Tarjan solved this pathology by replacing single rotations with two-level bottom-up double rotations that inspect both the parent p = parent(x) and the grandparent g = parent(p) of the target node x:

  1. Zig Step (Terminal Single Rotation, when p is the tree root): If x has no grandparent, perform a single rotation on edge (x, p) to make x the root. A zig step occurs at most once at the very end of a splay operation when the initial depth of x is odd.
  2. Zig-Zig Step (Homogeneous Double Rotation, when x and p are both left children or both right children): When x is the left child of p and p is the left child of g (or symmetrically both are right children), first rotate p with its parent g, and then rotate x with p. Rotating the upper edge (p, g) before the lower edge (x, p) is the defining feature of splaying: it folds the path in half, reducing the depth of every ancestor on the path by roughly a factor of two.
  3. Zig-Zag Step (Heterogeneous Double Rotation, when x is a left child and p is a right child, or vice versa): Perform the standard double rotation familiar from AVL trees—first rotate x with its parent p, and then rotate x with its former grandparent g—placing x above both p and g as its two children.

Dictionary Operations via Splaying

All standard ordered-dictionary operations reduce directly to Splay(x, T):

  • Search(k, T): Walk down the tree from the root as in a standard binary search tree. If key k is found at node x, call Splay(x, T) and return x; if the search reaches a null pointer below parent node y, call Splay(y, T) so the failed search path is still compressed.
  • Join(L, R): Given two splay trees where all keys in L are smaller than all keys in R, find the maximum element m in L, call Splay(m, L) (so m becomes the root of L with a null right child), and attach R as the right child of m.
  • Split(k, T): Search for k in T (splaying the closest key to the root) and sever either the left or right child pointer of the root.
  • Insert(k, T) and Delete(k, T): Insertion splits T at k and places the new node as the root above the two split halves (or inserts as a leaf and splays to the root); deletion splays the target node x to the root, removes it, and returns Join(x.left, x.right).

Potential-Function Amortized Analysis and Optimality Theorems

The Access Lemma

To analyze splaying over arbitrary positive node weights w(u) > 0, define the size of a node u in current tree T as the sum of weights in its subtree, s(u) = ∑v∈subtree(u) w(v), and define the rank of u as r(u) = log2 s(u). The global potential of tree T is the sum of ranks of all nodes:

Φ(T) = ∑u∈T r(u) = ∑u∈T log2 s(u)

Using the concavity of the logarithm (specifically, if α + β ≤ γ for positive reals, then log2 α + log2 β ≤ 2 log2 γ − 2), Sleator and Tarjan proved that the amortized cost a = (rotations) + ΔΦ of a single zig-zig or zig-zag step moving x from rank r(x) to post-step rank r'(x) satisfies:

azig-zig ≤ 3 (r'(x) − r(x)) and azig-zag ≤ 3 (r'(x) − r(x))

while the terminal zig step (if present) satisfies azig ≤ 3(r'(x) − r(x)) + 1. Summing across all steps of Splay(x, T) yields a telescoping sum that collapses to the Access Lemma: the amortized number of rotations to splay node x in a tree rooted at t is at most:

A(x, t) ≤ 3 (r(t) − r(x)) + 1 = 3 log2(s(t) / s(x)) + 1

Distribution-Sensitive Bounds From Weight Assignment

Because the weights w(u) appear only in the mathematical analysis and never in the code, choosing different positive weight assignments in the Access Lemma immediately proves four structural theorems for the exact same splay tree algorithm:

  1. Balance Theorem (w(u) = 1/n): Setting uniform weights gives s(t) = 1 and s(x) ≥ 1/n, proving O(log n) amortized time per operation.
  2. Static Optimality Theorem (w(u) = q(u), access frequency): Setting each node's weight to its empirical access probability q(u) proves that the amortized access cost for u is O(1 + log(1/q(u))), matching the Shannon entropy bound ∑ q(u) log(1/q(u)) of an optimal static binary search tree.
  3. Static Finger Theorem (w(u) = 1 / (|u − f| + 1)2): Accessing an element at rank distance d from any fixed finger element f takes O(log(d + 2)) amortized time.
  4. Working-Set Theorem: If ti distinct elements have been accessed since the previous access to element xi, the amortized cost of accessing xi is O(log(ti + 2)).
Self-Balancing Search TreeWorst-Case Single OpAmortized OpBalance Space OverheadAdapts to Skewed Access Distributions
Splay Tree (1985)O(n)O(log n)0 bits per nodeYes (Static Optimality & Working-Set)
Red–Black Tree (1978)O(log n)O(log n)1 bit per nodeNo (uniform depth ≈ log2 n)
AVL Tree (1962)O(log n)O(log n)2 bits / height intNo (height ≤ 1.44 log2 n)
Treap (1989)O(n) (O(log n) whp)O(log n) expected32–64 bits priorityNo (unless weighted random treap)
Tango Tree (Demaine et al., 2007)O(log log n) competitiveO(log log n) competitivePreferred-child bitsYes (provably O(log log n)-competitive)

Top-Down Splaying, Link–Cut Trees, and Dynamic Optimality

Top-Down Splaying and Link–Cut Tree Auxiliary Paths

In their original paper, Sleator and Tarjan also introduced top-down splaying, which eliminates parent pointers and upward stack traversal by splitting the tree into a left tree L, middle tree M, and right tree R during the downward search walk and reassembling them at the bottom. In dynamic graph algorithms, splay trees form the auxiliary preferred-path trees of link–cut trees (Sleator & Tarjan, 1983), enabling O(log n) amortized path aggregation, dynamic minimum spanning tree maintenance, and O(VE log V) Dinic blocking-flow computations.

The Dynamic Optimality Conjecture

Sleator and Tarjan conjectured in 1985 that splay trees are O(1)-competitive against every valid online or offline binary search tree algorithm—meaning that on any sequence of m accesses over n keys, the total rotation cost of a splay tree is within a constant factor of the minimum cost achievable by any sequence of binary search tree rotations, even one chosen with full future knowledge of the access sequence. Known as the Dynamic Optimality Conjecture, this remains one of the central open problems in theoretical computer science; proved special cases include the Sequential Access Theorem (Tarjan, 1985, showing that scanning all n keys in sorted order takes O(n) total time) and the Deque and Split Theorems (Lucas, 1988; Pettie, 2008).

Frequently Asked Questions

Why does a zig-zig step rotate the grandparent edge before the parent edge?

If a left-left chain g–p–x were rotated using two bottom-up single rotations (first rotating x with p, then rotating x with g), the path above x would remain an unshortened linear chain, allowing adversarial access sequences to take Ω(n) time per operation. By rotating p with g first and x with p second, the zig-zig step pairs adjacent ancestors into balanced branches, halving the depth of every node along the accessed path.

When can a splay tree perform poorly compared with a red–black tree or B-tree?

Because every read-only Search operation mutates the tree structure by splaying the accessed node to the root, splay trees perform memory writes on every lookup. In multi-threaded environments, this requires exclusive write locks or complex synchronization even for read-only workloads, and on uniform random access patterns—where temporal locality offers no advantage—the extra pointer writes and cache invalidations make splay trees slower than read-only B-trees or red–black trees.

What does the Working-Set Theorem guarantee in caching workloads?

The Working-Set Theorem proves that if an application repeatedly queries a small active subset of k keys out of a dictionary of n ≫ k total keys, those k keys remain clustered within O(log k) depth of the root, so each access takes O(log k) amortized time rather than O(log n) time—automatically providing self-tuning least-recently-used (LRU) cache behavior inside a single search tree.

References

  1. Sleator, D. D., & Tarjan, R. E. (1985). "Self-adjusting binary search trees." Journal of the ACM, 32(3), 652–686. https://doi.org/10.1145/3828.3835
  2. Tarjan, R. E. (1985). "Sequential access in splay trees takes linear time." Combinatorica, 5(4), 367–378. https://doi.org/10.1007/BF02579253
  3. Sleator, D. D., & Tarjan, R. E. (1983). "A data structure for dynamic trees." Journal of Computer and System Sciences, 26(3), 362–391. https://doi.org/10.1016/0022-0000(83)90006-590006-5)
  4. Demaine, E. D., Harmon, D., Iacono, J., & Pătraşcu, M. (2007). "Dynamic optimality—almost." SIAM Journal on Computing, 37(1), 240–251. https://doi.org/10.1137/S0097539705447347
🧬 Support Open Science
Explore interactive science games on LowKey Games or browse scientific apparel & accessories in the CRV Science Shop.
💡 Spot an inaccuracy or have primary literature to expand this article?
Submit a citation or revision note using your shared CRV Science / LowKey Games profile ID.
Suggest Revision