Splay Tree [1] is a self-binary search tree that achieves O(logn) 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 x 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 x to the root.
For example, the simplest one rotates x 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 x the node being splayed, y its parent and z its grandparent (if they exist).
Root case: x is the root. Do nothing.
Zig
The Zig case: y is the root. We just perform rotate(x,y).
See the following figure.
Otherwise, x has a grandparent z. It is further divided into two categories:
Zig-Zig
Zig-Zig case: both x and y are left children or right children. We first perform rotate(y,z) then rotate(x,y).
Zig-Zag
Zig-Zag case: x and y are different children of their parents, i.e., x is the left child and y is the right child, or vice versa. We first perform rotate(x,y) then rotate(y,z).
The splay is performed repeatedly on x 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 Ω(logn).
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 x.
T(x): the subtree rooted at x.
w(x): the weight associated with node x. This weight is only used for analysis and is not kept explicitly by splay tree.
s(x)=∑y∈T(x)wy: the sum of weights of nodes in T(x), which is referred to as the size of x.
r(x)≐logs(x), the rank of x.
Let T be the splay tree.
Definition. The potential function, which records the amount of "energy" in T, is defined as
Φ(T)=x∈T∑r(x).
Definition. The amortized cost of an operation is defined as
amortized cost=actual cost+ΔΦ
where ΔΦ is the change of the potential function.
Now, let r(x) be the rank of x before we performed a single splay operation on x and r′(x) the one after. The Access Lemma states the Φ changes as follows:
Access Lemma For a single splay operation, the potential change is bounded by
3(r′(x)−r(x))−2
for zig-zig and zig-zag case and
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 x to the root is
O(1+logs(x)s(root))
Proof.
To splay a node x to the root, suppose that we have performed k splays.
Further, let ri(x) be the rank of x after we performed the ith splay on x. We have
r0(x)=logs(x),rk(x)=logs(root).
Observe that the actual cost of splay operation is 2 for zig-zig and zig-zag case and 1 for the zig case.
Via the access lemma, the amortized cost is
where the first equality follows from r′(x)=r(z), and the first inequality holds since r(x)≤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)
□
Next, we analyze the amortized costs of basic BST operations.
Throughout the analysis we assume that w(x)=1.
Search
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(logn).
Remark: in case of unsuccessful search, we find either the predecessor or successor of the search key.
Insertion
Inserting a node x is the same as that in a BST.
Then we splay node x to the root.
We charge the cost of going down the tree by the cost of splaying x, which has amortized cost O(logn).
It is left to analyze the change of Φ.
Let root=x1,x2,...,xk,xk+1=x be the root to x path right after inserting x.
The insertion of x increases s(x1),s(x2),...,s(xk) by 1, therefore,
The last inequality follows from that s(xi)≥s(xi+1)+1 for i∈[k]. Therefore, ΔΦ=O(logn) and the overall amortized cost of insertion is O(logn).
Remark: key(xk) is either the predecessor or successor of key(x).
Deletion
As in a BST, deletion is a little bit tricky.
Let x be the node to delete and y be its parent (if exists). There are 3 cases
If x is a leaf node, then we just delete it and splay y to the root.
If x has only one child, we replace x with its child as the child of y. Then we delete x and splay y 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 x can be charged to the cost of splaying y to the root, which has amortized cost O(logn). 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(logn).
If x has two children, we find the predecessor node pred(x) of x, which is the node with largest key in x's left sub-tree. Note that pred(x) has at most one child. Then we delete the node pred(x). This reduces to case 1 or case 2. Finally we replace the key(x) with key(pred(x)).
Static Optimality
Let S be a sequence of m operations, consists only of successful searches.
Let pi>0 be the number of times the ith 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 S performed on the splay tree is at most a constant times the minimum possible cost of S performed a static binary search tree, plus O(m).
Proof.
Let
T∗ be the static binary search tree that minimizes the cost of S.
di the depth of the ith element in T∗, i.e. the number of nodes on the path from the root to the ith element.
In particular l(rootelement)=1.
The total search cost of S on T∗ is
i∈[n]∑pi⋅m⋅di.
We are going to prove that the cost for splay tree is
Om+i∈[n]∑pi⋅m⋅di.
To investigate the time for splay tree, we choose a different weight than earlier and just set wi=3−di.
Note that for each k∈N+, there can be at most 2k elements with depth k.
Hence the sum of weights is bounded by
i∈[n]∑wi=i∈[n]∑3di1≤k≥1∑2k⋅3k1∈O(1).
It follows that
s(root)=logi∈[n]∑wi∈O(1).
Further, for each i∈[n], since s(i)≥wi, via the access lemma, the amortized cost of accessing ith element is bounded by
O(1+log2s(i)s(root))⊂O(1+log2wi1)=O(1+di)
For each k∈[m], let ck be the actual cost of the kth operation in S and ak be the amortized cost.
Further, let Φk be the potential of the splay tree after the kth operation.
Then
It is left to bound Φ0−Φm.
Let r0(i) be the rank of ith before and rm(i) be the rank after the operations.
It holds that r0(i)≤r(root)∈O(1), and rm(i)≥log2w(i). Now,
Note that nlogn 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.
Suppose that S={x1,x2,...,xm} and let t(k) be the number of different nodes accessed before xk since the last access of node xk, or since the beginning of the sequence if xk is the first access.
Theorem. The overall cost of S on the splay tree is bounded by
O(m+n⋅logn+k=1∑nlogt(k))
Proof. We maintain w(x)=(j+1)21, if the sequence accesses j different nodes since the last accessed of node x, or w(x)=n21 if it is never accessed before. By definition of t(k), we have w(xk)=(t(xk)+1)21. Now
W≐x∈T∑nw(x)∈O(1)
For a node xk , if it is accessed for the first time, the amortized cost is
O(1+logw(xk)W)=O(logn)=O(logt(k))
Summing over all nodes x in the splay tree T, we get O(nlogn).
Otherwise, if an item xk is accessed before, the amortized cost of accessing xk is
O(1+logw(xk)W)=O(1+logt(k))
After each access of a node xk and splaying it to the root, we modify the weights of the nodes as follows: for all node x with t(x)<t(xk) (the nodes accessed after the last access of xk), we set
w(x)=(t(x)+1)21
This guarantees that w(xk)=1/(1+t(k))2 for k∈[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+logt(k)).
Hence
k∈[m]∑ak∈Onlogn+m+k∈[m]∑logt(k).
Finally, it is easy to see that Φ0−Φm∈O(nlogn). Therefore,
k∈[m]∑ck∈Onlogn+m+k∈[m]∑logt(k).
□
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,...,n. For a node x, we use π(x) to denote its order.
Theorem. For any fixed i∈[n], the total cost of S on the splay tree is bounded by
Onlogn+m+k∈[m]∑log(∣π(xk)−i∣+1).
Proof. For x∈T, we set w(x)=(∣pi(x)−i∣+1)21. Then
W≐x∈T∑∈O(1)
and the amortized cost of accessing xk is
O(1+logw(xk)W)=O(1+log(∣π(xk)−i∣+1))
The theorem then follows from Φ0−Φm=O(nlogn).
□
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.