Splay Tree [1] is a self-binary search tree that achieves O(log⁡n)O(\log n) amortized time complexity for each operation. Unlike other balanced binary search trees, it does not require to maintain additional information to keep balance. Further, it has good property that more recently accessed nodes are more easily retrieved and achieves certain optimality even for an unknown sequence of operations.

Basic Operations

The philosophy of splay tree is very simple: just bring the last accessed node xx to the root of the tree, via a sequence of rotations. This brings the most recently visited nodes close to the root.

[Remark: For insertion, the last accessed node is the node inserted. For a successful search operation, the last accessed node is the node found. For an unsuccessful search/deletion, it is the predecessor or successor of the search node / deleted node.]

There is more than one way to rotate a node xx to the root. For example, the simplest one rotates xx with its parent repeatedly until it becomes the root. However, we can easily find counter examples for this naïve approach. The rotations need to be carefully designed and are called splays. At each step the node being splayed is brought up at most 2 levels. It can be divided into three categories.

Call xx the node being splayed, yy its parent and zz its grandparent (if they exist).

  1. Root case: xx is the root. Do nothing.

Zig

  1. The Zig case: yy is the root. We just perform rotate(x,y)rotate(x, y).

    • See the following figure.
  2. Otherwise, xx has a grandparent zz. It is further divided into two categories:

Zig-Zig

  • Zig-Zig case: both xx and yy are left children or right children. We first perform rotate(y,z)rotate(y, z) then rotate(x,y)rotate(x, y).

Zig-Zag

  • Zig-Zag case: xx and yy are different children of their parents, i.e., xx is the left child and yy is the right child, or vice versa. We first perform rotate(x,y)rotate(x, y) then rotate(y,z)rotate(y, z).

The splay is performed repeatedly on xx until it reaches the root. It is also possible to perform equivalent operations in an top-down approach [1].

Amortized Analysis

There is no explicit control over the height of the tree. It is possible that the accessed node has depth Ω(log⁡n)\Omega(\log n). The intuition is that, however, the imbalanced situation is not easy to create and results from a sequence of previous operations. The number such operations can not be too small to create the imbalance. We can charge the cost of accessing the node with long depth to these previous operations. Intuitively, each such operation is required to reserve some additional "energy" for the amount of imbalanced it creates, which is used later to fix the imbalance. When a long path is encountered, there should be enough "energy" to cover the search on the path, and the subsequent splay costs.

It is left to give a quantitative analysis for keeping the "energy" and for "using" the "energy". We need a few more definitions for a node xx.

  1. T(x):T(x): the subtree rooted at xx.

  2. w(x):w(x): the weight associated with node xx. This weight is only used for analysis and is not kept explicitly by splay tree.

  3. s(x)=∑y∈T(x)wy:s(x)=\sum_{y \in T(x)} w_y: the sum of weights of nodes in T(x)T(x), which is referred to as the size of xx.

  4. r(x)≐log⁡s(x)r(x) \doteq \log s(x), the rank of xx.

Let TT be the splay tree.

Definition. The potential function, which records the amount of "energy" in TT, is defined as

Φ(T)=∑x∈Tr(x). \Phi(T) = \sum_{x \in T} r(x).

Definition. The amortized cost of an operation is defined as

amortized cost=actual cost+ΔΦ \text{amortized cost} = \text{actual cost} + \Delta \Phi

where ΔΦ\Delta \Phi is the change of the potential function.

Now, let r(x)r(x) be the rank of xx before we performed a single splay operation on xx and r′(x)r'(x) the one after. The Access Lemma states the Φ\Phi changes as follows:

Access Lemma For a single splay operation, the potential change is bounded by

3(r′(x)−r(x))−2 3(r'(x) - r(x)) - 2

for zig-zig and zig-zag case and

r′(x)−r(x) r'(x) - r(x)

for the zig case.

Before we prove this key lemma, we use it for the following theorem.

Theorem. The amortized cost of the splaying a node xx to the root is

O(1+log⁡s(root)s(x)) O \left( 1 + \log \frac{s(root) }{s(x) } \right)

Proof. To splay a node xx to the root, suppose that we have performed kk splays. Further, let ri(x)r_i(x) be the rank of xx after we performed the ithi^{th} splay on xx. We have

r0(x)=log⁡s(x),rk(x)=log⁡s(root). r_0(x) = \log s(x), \qquad r_k(x) = \log s(root).

Observe that the actual cost of splay operation is 22 for zig-zig and zig-zag case and 1 for the zig case. Via the access lemma, the amortized cost is

amortized cost≤∑i∈[k]3(ri(x)−ri−1(x))+1=3(rk(x)−r0(x))+1. \begin{aligned} \text{amortized cost} &\le \sum_{i \in [k] } 3 \big( r_i(x) - r_{i - 1}(x) \big) +1 \\ &= 3(r_k(x) - r_0(x)) + 1. \end{aligned}

The added 1 results from the final possible zig operation.

□\square

If we set w(x)=1w(x) = 1 for all nodes, then rk(x)=log⁡nr_k(x) = \log n and r1(x)≥log⁡1=0r_1(x) \ge \log 1 = 0. Therefore, the amortized cost of splaying xx is bounded by O(log⁡n)O(\log n).

Proof of The Access Lemma

Zig-Zig

  • Example.

First,

12r′(z)+12r(x)=12log⁡s′(z)+12log⁡s(x)≤log⁡[s′(z)2+s(x)2]<log⁡[s(z)2]. \begin{aligned} \frac{1}{2} r'(z) + \frac{1}{2} r(x) &= \frac{1}{2} \log s'(z) + \frac{1}{2} \log s(x) \\ &\le \log \left[ \frac{s'(z)}{2} + \frac{ s(x) }{2} \right] \\ &< \log \left[ \frac{s(z)}{2} \right]. \end{aligned}

The first inequality follows from that the function log⁡(⋅)\log (\cdot ) is concave and the second one follows from s′(z)+s(x)=s(z)−1<s(z)s'(z) + s(x) = s(z) - 1 < s(z).

Noting that s(z)=s(x′)s(z) = s(x'), we have r(z)=r(x′)r(z) = r(x'), and

r′(z)+r(x)≤2[r(z)−1]=2[r(x′)−1]. \begin{aligned} & r'(z) + r(x) \le 2 \left[ r(z) - 1 \right] = 2 \left[ r(x') - 1 \right]. \end{aligned}

This implies that

r′(z)≤2[r(x′)−1]−r(x). r'(z) \le 2 \left[ r(x') - 1 \right] - r(x).

Now consider the potential change of the zig-zig operation:

r′(x)+r′(y)+r′(z)−r(x)−r(y)−r(z)=r′(y)+r′(z)−r(x)−r(y)≤r′(x)+r′(z)−2r(x)=r′(x)+[2[r(x′)−1]−r(x)]−2r(x)≤3[r′(x)−r(x)]−2, \begin{array}{rll} & r'(x) + r'(y) + r'(z) - r(x) - r(y) - r(z) \\ = & r'(y) + r'(z) - r(x) - r(y) \\ \le & r'(x) + r'(z) - 2 r(x) \\ = & r'(x) + \left[ 2 \left[ r(x') - 1 \right] - r(x) \right] - 2 r(x) \\ \le & 3[r'(x) - r(x)] - 2, \end{array}

where the first equality follows from that r′(x)=r(z)r'(x) = r(z), and the first inequality holds since r(x)≤r(y)r(x) \le r(y).

Zig-Zag

  • Example.

First, note that

12r′(y)+12r′(z)=12log⁡s′(y)+12log⁡s′(z)≤log⁡[s′(y)2+s′(z)2]<log⁡[s′(x)2]. \begin{aligned} \frac{1}{2} r'(y) + \frac{1}{2} r'(z) = & \frac{1}{2} \log s'(y) + \frac{1}{2} \log s'(z) \\ \le & \log \left[ \frac{s'(y)}{2} + \frac{ s'(z) }{2} \right] \\ < & \log \left[ \frac{s'(x)}{2} \right]. \end{aligned}

The first inequality follows from that the function log⁡(⋅)\log (\cdot ) is concave and the second one follows from s′(y)+s′(z)=s′(x)−1<s′(x)s'(y) + s'(z) = s'(x) - 1 < s'(x).

Therefore,

r′(y)+r′(z)≤2[r′(x)−1] r'(y) + r'(z) \le 2 \left[ r'(x) - 1 \right]

Now consider the potential change of the zig-zag operation:

r′(x)+r′(y)+r′(z)−r(x)−r(y)−r(z)=r′(y)+r′(z)−r(x)−r(y)≤r′(y)+r′(z)−2r(x)≤2[r′(x)−1]−2r(x)=2[r′(x)−r(x)]−2, \begin{array}{rll} & r'(x) + r'(y) + r'(z) - r(x) - r(y) - r(z) \\ = & r'(y) + r'(z) - r(x) - r(y) \\ \le & r'(y) + r'(z) - 2 r(x) \\ \le & 2 \left[ r'(x) - 1 \right] - 2 r(x) \\ = & 2[r'(x) - r(x)] - 2, \end{array}

where the first equality follows from r′(x)=r(z)r'(x) = r(z), and the first inequality holds since r(x)≤r(y)r(x) \le r(y).

Zig

  • Example.

Now consider the potential change of the zig-zag operation:

r′(x)+r′(y)−r(x)−r(y)=r′(y)−r(x)≤r′(x)−r(x) \begin{array}{rll} & r'(x) + r'(y) - r(x) - r(y) \\ = & r'(y) - r(x) \\ \le & r'(x) - r(x) \end{array}

□\square

Next, we analyze the amortized costs of basic BST operations. Throughout the analysis we assume that w(x)=1w(x) = 1.

Whether the search is successful or not, we splay the last accessed node to the root and charge the cost of going down the tree to the splay-operations. Therefore, the amortized cost is O(log⁡n)O(\log n).

Remark: in case of unsuccessful search, we find either the predecessor or successor of the search key.

Insertion

Inserting a node xx is the same as that in a BST. Then we splay node xx to the root. We charge the cost of going down the tree by the cost of splaying xx, which has amortized cost O(log⁡n)O(\log n). It is left to analyze the change of Φ\Phi. Let root=x1,x2,...,xk,xk+1=xroot = x_1, x_2, ..., x_k, x_{k + 1} = x be the root to xx path right after inserting xx. The insertion of xx increases s(x1),s(x2),...,s(xk)s(x_1), s(x_2), ..., s(x_{k }) by 1, therefore,

ΔΦ=∑i=1klog⁡s(xi)+1s(xi)=log⁡∏i=1ks(xi)+1s(xi)≤log⁡s(x1)+1s(xk), \begin{aligned} \Delta \Phi &= \sum_{i = 1}^k \log \frac{s(x_i) + 1 }{s(x_i)} \\ &= \log \prod_{i = 1}^k \frac{s(x_i) + 1 }{s(x_i)} \\ &\le \log \frac{s(x_1) + 1}{ s(x_k ) }, \end{aligned}

The last inequality follows from that s(xi)≥s(xi+1)+1s(x_i) \ge s(x_{i + 1}) + 1 for i∈[k]i \in [k]. Therefore, ΔΦ=O(log⁡n)\Delta \Phi = O(\log n) and the overall amortized cost of insertion is O(log⁡n)O(\log n).

Remark: key(xk)key(x_{k}) is either the predecessor or successor of key(x)key(x).

Deletion

As in a BST, deletion is a little bit tricky. Let xx be the node to delete and yy be its parent (if exists). There are 3 cases

  • If xx is a leaf node, then we just delete it and splay yy to the root.

  • If xx has only one child, we replace xx with its child as the child of yy. Then we delete xx and splay yy to the root.

The final case uses the first two cases as a sub-routine. We first discuss the amortized cost of the first two cases. The cost of going down to xx can be charged to the cost of splaying yy to the root, which has amortized cost O(log⁡n)O(\log n). Deleting the node decreases the potential function, which will only lower the overall amortized cost. Therefore, the amortized cost of deletion is bounded by O(log⁡n)O(\log n).

  • If xx has two children, we find the predecessor node pred(x)pred(x) of xx, which is the node with largest key in xx's left sub-tree. Note that pred(x)pred(x) has at most one child. Then we delete the node pred(x)pred(x). This reduces to case 1 or case 2. Finally we replace the key(x)key(x) with key(pred(x))key(pred(x)).

Static Optimality

Let SS be a sequence of mm operations, consists only of successful searches. Let pi>0p_i > 0 be the number of times the ithi^{th} element is accessed. Define a static binary search tree to be the one whose structure is fixed. In particular, it can not perform rotations.

Theorem (Static Optimality). The total cost of SS performed on the splay tree is at most a constant times the minimum possible cost of SS performed a static binary search tree, plus O(m)O(m).

Proof.
Let

  1. T∗T^* be the static binary search tree that minimizes the cost of SS.

  2. did_i the depth of the ithi^{th} element in T∗T^*, i.e. the number of nodes on the path from the root to the ithi^{th} element. In particular l(root element)=1l(root\, element) = 1.

The total search cost of SS on T∗T^* is

∑i∈[n]pi⋅m⋅di. \sum_{ i \in [n] } p_i \cdot m \cdot d_i.

We are going to prove that the cost for splay tree is

O(m+∑i∈[n]pi⋅m⋅di). O \left( m + \sum_{ i \in [n] } p_i \cdot m \cdot d_i \right).

To investigate the time for splay tree, we choose a different weight than earlier and just set wi=3−diw_i = 3^{-d_i}. Note that for each k∈N+k \in \mathbb{N}^+, there can be at most 2k2^k elements with depth kk. Hence the sum of weights is bounded by

∑i∈[n]wi=∑i∈[n]13di≤∑k≥12k⋅13k∈O(1). \sum_{i \in [n]} w_i = \sum_{i \in [n]} \frac{1}{3^{d_i}} \le \sum_{k \ge 1} 2^k \cdot \frac{1}{3^k} \in O(1).

It follows that

s(root)=log⁡∑i∈[n]wi∈O(1). s(root) = \log \sum_{i \in [n]} w_i \in O(1).

Further, for each i∈[n]i \in [n], since s(i)≥wis(i) \ge w_i, via the access lemma, the amortized cost of accessing ithi^{th} element is bounded by

O(1+log⁡2s(root)s(i))⊂O(1+log⁡21wi)=O(1+di) O \left(1 + \log_2 \frac{s(root)}{s(i)} \right) \subset O \left(1 + \log_2 \frac{1}{w_i} \right) = O \left(1 + d_i \right)

For each k∈[m]k \in [m], let ckc_k be the actual cost of the kthk^{th} operation in SS and aka_k be the amortized cost. Further, let Φk\Phi_k be the potential of the splay tree after the kthk^{th} operation. Then

∑k∈[m]ak=∑k∈[m](ck+Φk−Φk−1)=Φm−Φ0+∑k∈[m]ck. \sum_{ k \in [m] } a_k = \sum_{ k \in [m] } \big( c_k + \Phi_k - \Phi_{k - 1} \big) = \Phi_m - \Phi_0 + \sum_{ k \in [m] } c_k.

Hence, the actual cost on this sequence is bounded by

∑k∈[m]ck=Φ0−Φm+∑k∈[m]ak∈O(Φ0−Φm+∑i∈[n]pi⋅m⋅(1+di))=O(Φ0−Φm+∑i∈[n]pi⋅m⋅(di+1))=O(m+Φ0−Φm+∑i∈[n]pi⋅m⋅di) \begin{aligned} \sum_{ k \in [m] } c_k &= \Phi_0 - \Phi_m + \sum_{ k \in [m] } a_k \\ &\in O \left( \Phi_0 - \Phi_m + \sum_{ i \in [n] } p_i \cdot m \cdot \big( 1 + d_i \big) \right) \\ &= O \left( \Phi_0 - \Phi_m + \sum_{ i \in [n] } p_i \cdot m \cdot (d_i + 1) \right) \\ &= O \left( m + \Phi_0 - \Phi_m + \sum_{ i \in [n] } p_i \cdot m \cdot d_i \right) \end{aligned}

It is left to bound Φ0−Φm\Phi_0 - \Phi_m. Let r0(i)r^0(i) be the rank of ithi^{th} before and rm(i)r^m(i) be the rank after the operations. It holds that r0(i)≤r(root)∈O(1)r^0(i) \le r(root) \in O(1), and rm(i)≥log⁡2w(i)r^m(i) \ge \log_2 w(i). Now,

Φ0−Φm=∑i∈[n](r0(i)−rm(i))∈O(∑i∈[n]log⁡3di)=O(∑i∈[n]di). \begin{aligned} \Phi_0 - \Phi_m = \sum_{i \in [n]} \left( r^0(i) - r^m(i) \right) &\in O \left( \sum_{ i \in [n] } \log 3^{d_i} \right) = O \left( \sum_{i \in [n]} d_i \right). \end{aligned}

Under the assumption that each item is accessed at least once, we know pi⋅m≥1p_i \cdot m \ge 1 and

Φ0−Φm∈O(∑i∈[n]pi⋅m⋅di). \Phi_0 - \Phi_m \in O \left( \sum_{ i \in [n] } p_i \cdot m \cdot d_i \right).

Remark. Of course, we could bound

∑i∈[n]dilog⁡3≤(1+2⋅2+3⋅22+4⋅23+...+(⌊log⁡2n⌋+1)⋅2⌊log⁡2n⌋)log⁡3. \sum_{ i \in [n] } d_i \log 3 \le (1 + 2 \cdot 2 + 3 \cdot 2^2 + 4 \cdot 2^3 + ... + (\lfloor \log_2 n \rfloor + 1) \cdot 2^{ \lfloor \log_2 n \rfloor }) \log 3.

Let Σ=1+2⋅2+3⋅22+4⋅23+...+(⌊log⁡2n⌋+1)⋅2⌊log⁡2n⌋\Sigma=1 + 2 \cdot 2 + 3 \cdot 2^2 + 4 \cdot 2^3 + ... + (\lfloor \log_2 n \rfloor + 1) \cdot 2^{ \lfloor \log_2 n \rfloor }, then

2⋅Σ−Σ=(⌊log⁡2n⌋+1)⋅2⌊log⁡2n⌋+1−∑i=0⌊log⁡2n⌋−12i∈O(nlog⁡n) 2 \cdot \Sigma - \Sigma = (\lfloor \log_2 n \rfloor + 1) \cdot 2^{ \lfloor \log_2 n \rfloor + 1} - \sum_{i = 0}^{\lfloor \log_2 n \rfloor - 1} 2^i \in O(n \log n)

Note that nlog⁡nn \log n is a lower bound of the access cost of the optimal static binary search tree, under the assumption that each item is accessed at least once.

Remark:

In general,

∑i=0kxi=xk+1−1x−1 \sum_{i = 0}^k x^i = \frac{x^{k + 1} - 1}{x - 1}

Applying x⋅∂∂xx \cdot \frac{\partial}{\partial x} to both sides,

∑i=1kixi=x⋅(k+1)xk(x−1)−(xk+1−1)(x−1)2=x⋅(k+1)(xk+1−xk)−(xk+1−1)(x−1)2=x⋅kxk+1−(k+1)xk+1(x−1)2=x⋅(kxkx−1−xk−1(x−1)2) \begin{aligned} \sum_{i = 1}^k i x^{i} &= x \cdot \frac{(k + 1) x^k (x - 1) - (x^{k + 1} - 1)}{(x - 1)^2} \\ &= x \cdot \frac{(k + 1) (x^{k +1} - x^k) - (x^{k + 1} - 1)}{(x - 1)^2} \\ &= x \cdot \frac{ k x^{k +1} - (k + 1) x^k + 1}{(x - 1)^2} \\ &= x \cdot \left( \frac{k x^k }{x - 1} - \frac{ x^k - 1}{(x - 1)^2} \right)\\ \end{aligned}

Working Set Property

Suppose that S={x1,x2,...,xm}S = \{x_1, x_2, ..., x_m\} and let t(k)t(k) be the number of different nodes accessed before xkx_k since the last access of node xkx_k, or since the beginning of the sequence if xkx_k is the first access.

Theorem. The overall cost of SS on the splay tree is bounded by

O(m+n⋅log⁡n+∑k=1nlog⁡t(k)) O \left( m + n \cdot \log n + \sum_{k = 1}^n \log t(k) \right)

Proof. We maintain w(x)=1(j+1)2w(x) = \frac{1}{(j + 1)^2}, if the sequence accesses jj different nodes since the last accessed of node xx, or w(x)=1n2w(x) = \frac{1}{n^2} if it is never accessed before. By definition of t(k)t(k), we have w(xk)=1(t(xk)+1)2w(x_k) = \frac{1}{(t(x_k) + 1)^2}. Now

W≐∑x∈Tnw(x)∈O(1) W \doteq \sum_{x \in T}^n w(x) \in O(1)

For a node xkx_k , if it is accessed for the first time, the amortized cost is

O(1+log⁡Ww(xk))=O(log⁡n)=O(log⁡t(k)) O \left( 1 + \log \frac{W}{w(x_k)} \right) = O(\log n) = O(\log t(k))

Summing over all nodes xx in the splay tree TT, we get O(nlog⁡n)O(n \log n).

Otherwise, if an item xkx_k is accessed before, the amortized cost of accessing xkx_k is

O(1+log⁡Ww(xk))=O(1+log⁡t(k)) O\left( 1 + \log \frac{W}{w(x_k) } \right) = O \left( 1 + \log t(k) \right)

After each access of a node xkx_k and splaying it to the root, we modify the weights of the nodes as follows: for all node xx with t(x)<t(xk)t(x) < t(x_k) (the nodes accessed after the last access of xkx_k), we set

w(x)=1(t(x)+1)2 w(x) = \frac{1}{(t(x) + 1)^2}

This guarantees that w(xk)=1/(1+t(k))2w(x_k) = {1} / {(1 + t(k))^2} for k∈[m]k \in [m].

The weight of the root does not change but the weight of other nodes may decrease after the reassignment. Therefore, the overall potential decreases. The amortized cost of the this access only decreases and is still bounded by O(1+log⁡t(k))O(1 + \log t(k)).

Hence

∑k∈[m]ak∈O(nlog⁡n+m+∑k∈[m]log⁡t(k)). \sum_{ k \in [m] } a_k \in O \left( n \log n + m + \sum_{ k \in [m] } \log t(k) \right).

Finally, it is easy to see that Φ0−Φm∈O(nlog⁡n)\Phi_0 - \Phi_m \in O(n \log n). Therefore,

∑k∈[m]ck∈O(nlog⁡n+m+∑k∈[m]log⁡t(k)). \sum_{ k \in [m] } c_k \in O \left( n \log n + m + \sum_{ k \in [m] } \log t(k) \right).

□\square

Static Finger Property

The static finger property states that splay tree achieves some kind of locality. Suppose we order the nodes in the tree in increasing order according to their keys and labels them as 1,2,...,n1, 2, ..., n. For a node xx, we use π(x)\pi(x) to denote its order.

Theorem. For any fixed i∈[n]i \in [n], the total cost of SS on the splay tree is bounded by

O(nlog⁡n+m+∑k∈[m]log⁡(∣π(xk)−i∣+1)). O \left( n \log n + m + \sum_{ k \in [m] } \log (|\pi(x_k) - i| + 1) \right).

Proof. For x∈Tx \in T, we set w(x)=1(∣pi(x)−i∣+1)2w(x) = \frac{1}{(|pi(x) - i| + 1)^2}. Then

W≐∑x∈T∈O(1) W \doteq \sum_{x \in T} \in O(1)

and the amortized cost of accessing xkx_k is

O(1+log⁡Ww(xk))=O(1+log⁡(∣π(xk)−i∣+1)) O \left( 1 + \log \frac{W}{w(x_k)} \right) = O(1 + \log (|\pi(x_k) - i| + 1))

The theorem then follows from Φ0−Φm=O(nlog⁡n)\Phi_0 - \Phi_m = O(n \log n).

□\square

Reference

[1]. Sleator, D.D. and Tarjan, R.E., 1983. A data structure for dynamic trees. Journal of computer and system sciences, 26(3), pp.362-391.

[2]. Jelani Nelson, Lecture 7, CS 224: Advanced Algorithms.