Lecture 17: Splay Trees

Handout

June 12th, 2002

1 Amortized Analysis

• Last time we discussed amortized analysis of data structures

– A way of expressing that even though the worst-case performance of an operation can be bad, the total performance of a sequence of operations cannot be too bad.

• One way of thinking of amortized time is as being an “average”: If any sequence of n operations takes less than T (n) time, the amortized time per operation is T (n)/n.

• We formally defined amortized time using the idea that we over-charge some operations and store the over-charge as credits/potential that can then help pay for later operations (potential method)

– Consider performing n operations on an initial data structure D_{0}

– D_{i} is data structure after ith operation.

– c_{i} is actual cost (time) of ith operation.

– Potential function: Φ : D_{i} → R

– c̃_{i} amortized cost of ith operation: c̃_{i} = c_{i} + Φ(D_{i}) − Φ(D_{i−1})

– Given Φ(D_{0}) = 0 and Φ(D_{i}) ≥ 0: ∑_{i=1}^{n} c_{i} ≤ ∑_{i=1}^{n} c̃_{i}

• We also discussed two examples of amortized analysis

– Stack with Multipop (O(n) worst-case, O(1) amortized).

– Increment on binary counter (O(log n) worst-case, O(1) amortized).

In both cases we could argue for O(1) amortized performance without actually doing potential calculation—we just think about potential/credits as being distributed on certain parts of the data structure and let operations put and take credits while maintaining some invariant (accounting method).

2 Splay trees

• We have previously discussed binary search trees and how they can be kept balanced (O(log n)

height) during insert and delete operations (red-black trees). – Rebalancing rather complicated

– Extra space used for the color of each node

• We also discussed skip lists which are a lot simpler than red-black trees – Only guarantee O(log n) expected performance

– No extra information is used for rebalance information though • Splay trees are search trees that “magically” balance themselves (no rebalance information is

stored) and have amortized O(log n) performance.

• Recall search trees:

– Binary tree with elements in nodes

– If node v holds element e then ∗ all elements in left subtree < e

∗ all elements in left subtree > e

• Splay tree:

– Normal (possibly unbalanced) search tree T

– All operations implemented using one basic operation, Splay:

Splay(x, T) searches for x in T and reorganizes tree such that x (or min element > x or max element < x) is in root

– Search(x, T): Splay(x, T) and inspect root

– Insert(x, T): Splay(x, T) and create new root with x – Delete(x, T):

∗ Splay(x, T) and remove root → tree falls into T 1 and T 2.

∗ Splay(x, T 1)

∗ Make T 2 right son of new root of T 1 after splay

⇓ All operations perform O(1) Splay’s and use O(1) extra time. ⇓ O(log n) amortized Splay gives O(log n) amortized bound on all operations.

• Implementation of Splay:

– Search for x like in normal search tree

– Repeatedly rotate x up until it becomes the root. We distinguish between three cases:

1. x is child of root (no grandparent): rotate(x)

e.g.

2. x has parent y and grandparent z and both x and y left (right) children: rotate(y) followed by rotate(x)

e.g.

3. x has parent y and grandparent z and one of x and y is a left child and the other is a right child: rotate(x) followed by rotate(x)

e.g.

• Note:

– A Splay can take O(n) worst-case time (very unbalanced tree)

– But Splay trees somehow seem to stay nicely balanced

Examples: Splay(1, T)

Splay(5, T)

• Analysis:

– We will use accounting method to show that all operations (Splay) takes O(log n) amor-

tized time.

∗ We will imagine that each node in tree has credits on it

∗ We will use some credits to pay for (part of) rotations during a splay

∗ We will see that we only have to place O(log n) new credits (on root) when perform-

ing an Insert or Delete

– Note that we will ignore cost of searching for x, since the rotations cost at least as much as the search (⇒ if we can bound amortized rotation cost we also bound search cost).

– Let T (x) be tree rooted at x. We will maintain the credit invariant that each node x holds μ(x) = ⌊log |T (x)|⌋ credits.

– We will prove the following lemma:

Less than or equal to 3(μ(T)−μ(x)+O(1)) credits are needed to perform Splay(x, T) operation and maintain credit invariant

– Using this lemma we get that a splay operation uses at most 3⌊log n⌋+O(1) = O(log n) credits (time).

– As an Insert or a Delete requires us to insert at most O(log n) extra credits (on the

root) more than the ones used on the Splay, we get the O(log n) amortized bound.

• Proof of lemma: – Let μ and μ′ be the value of μ before and after a rotate operation in case 1, 2, or 3.

– During a Splay operation we perform a number of, say k ≥ 0, case 2 and 3 operations and possibly a case 1 operation.

– Next time we will show that the cost of one operation is:

∗ Case 1: 3(μ′(x) − μ(x) + O(1))

∗ Case 2: 3(μ′(x) − μ(x))

∗ Case 3: 3(μ′(x) − μ(x)) ⇓ When we sum over all ≤ k + 1 operations in a splay we get 3(μ(T) − μ(x) + O(1)) where μ(x) is the number of credits on x before the Splay. Note that it is important that we only have the O(1) term in case 1.

• Case 1:

– We have: μ′(x) = μ(y), μ′(y) ≤ μ′(x) and all other μ’s are unchanged.

– To maintain invariant we use: \begin{aligned}μ′(x) + μ′(y) − μ(x) − μ(y) & = & μ′(y) − μ(x) \\ & ≤ & μ′(x) − μ(x) \\ & ≤ & 3(μ′(x) − μ(x)) \end{aligned}

– To do actual rotation we use O(1) credits.

• Case 2:

– We have μ′(x) = μ(z), μ′(y) ≤ μ′(x), μ′(z) ≤ μ′(x), μ(y) ≥ μ(x) and all other μ’s are unchanged.

– To maintain invariant we use:

μ′(x) + μ′(y) + μ′(z) − μ(x) − μ(y) − μ(z) = μ′(y) + μ′(z) − μ(x) − μ(y)
= (μ′(y) − μ(x)) + (μ′(z) − μ(y))
≤ (μ′(x) − μ(x)) + (μ′(x) − μ(x))
= 2(μ′(x) − μ(x))

– This means that we can use the remaining μ′(x)−μ(x) credits to pay for rotation, unless μ′(x) = μ(x) (can happen since μ(x) = ⌊log |T (x)|⌋).

– We will show that if μ′(x) = μ(x) then μ′(x) + μ′(y) + μ′(z) < μ(x) + μ(y) + μ(z) which means that the operation actually releases credits we can use for the rotation:

∗ Assume μ′(x) = μ(x) and μ′(x) + μ′(y) + μ′(z) ≥ μ(x) + μ(y) + μ(z)

∗ We have \begin{matrix}μ(z) = μ′(x) = μ(x) \\ ⇓ \\ μ(z) = μ(x) = μ(y) \end{matrix}

and \begin{aligned}μ′(x) + μ′(y) + μ′(z) & ≥ & μ(x) + μ(y) + μ(z) \\ & = & 3μ(x) \\ & = & 3μ′(x) \\ ⇓ \\ μ′(y) + μ′(z) ≥ 2μ′(x) \end{aligned}

∗ Since μ′(y) ≤ μ′(x) and μ′(z) ≤ μ′(x) we get μ′(x) = μ′(y) = μ′(z)

∗ Since μ(z) = μ′(x) we have μ(x) = μ(y) = μ(z) = μ′(x) = μ′(y) = μ′(z) which cannot be true (and thus our initial assumption cannot be true): Let a be |T (x)| before rotations (a = |T 1| + |T 2| + 1) Let b be |T (z)| after rotations (b = |T 3| + |T 4| + 1) Since μ(x) = μ′(z) = μ′(x) we have ⌊log a⌋ = ⌊log b⌋ = ⌊log(a + b + 1)⌋ but then we have the following contradiction:

· if a ≤ b: ⌊log(a + b + 1)⌋ ≥ ⌊log 2a⌋ = 1 + ⌊log a⌋ > ⌊log a⌋ · if a > b: ⌊log(a + b + 1)⌋ ≥ ⌊log 2b⌋ = 1 + ⌊log b⌋ > ⌊log b⌋

• Case 3:

– Can be proved analogously to case 2.