splay trees

splay trees
----------------------------------------------------------------------------
-- definition of splay trees
-- analysis of splay trees

Splay Trees (self-adjusting search trees)
-----------------------------------------

These notes just describe the bottom-up splaying algorithm, the proof of
the access lemma, and a few applications.

Every time a node is accessed in a splay tree, it is moved to the root
of the tree.  The amortized cost of the operation is O(log n).  Just
moving the element to the root by rotating it up the tree does not have
this property.  Splay trees do movement is done in a very special way
that guarantees this amortized bound.

I'll describe the algorithm by giving three rewrite rules in the form of
pictures.  In these pictures, x is the node that was accessed (that will
eventually be at the root of the tree).  By looking at the local
structure of the tree defined by x, x's parent, and x's grandparent we
decide which of the following three rules to follow.  We continue to
apply the rules until x is at the root of the tree:

                             y             x
   Zig (terminal case):     /     ====>     \
                           x                 y

                    z              z
                   /              /             x
   Zig-zag:       y     ====>    x   ====>     / \
                   \            /             y   z
                    x          y

                    z                         x
                   /            y              \
   Zig-zig:       y     ====>  / \   ====>      y
                 /            x   z              \
                x                                 z

Notes (1) Each rule has a mirror image variant, which covers all the cases.

      (2) The zig-zig rule is the one that distinguishes splaying from
          just rotating x to the root of the tree.

      (3) Top-down splaying is much more efficient in practice.  Code for
          doing this is on my web site (www.cs.cmu.edu/~sleator).

Here are some examples:

              6                0                       3
             /                  \                     / \
            5                    5                   /   \
           /                    / \                 /     \
          4     splay(0)       3   6    splay(3)   0       5
         /      =======>      / \       =======>    \     / \
        3                    1   4                   1   4   6
       /                      \                       \
      2                        2                       2
     /
    1
   /
  0

To analyze the performance of splaying, we start by assuming that each
node x has a weight w(x) > 0.  These weights can be chosen arbitrarily.
For each assignment of weights we will be able to derive a bound on the
cost of a sequence of accesses.  We can choose the assignment that gives
the best bound.  By giving the frequently accessed elements a high
weight, we will be able to get tighter bounds on the running time.
Note that the weights are only used in the analysis, and do not change
the algorithm at all.

(A commonly used case is to assign all the weights to be 1.)

Before we can state our performance lemma, we need to define two more
quantities.  The size of a node x (denoted s(x)) is the total weight of
all the nodes in the subtree rooted at x.  The rank of a node x (denoted
r(x)) is the floor(log_2) of the size of x.  Restating these:

          s(x) = Sum (over y in the subtree rooted at x) of w(y)

          r(x) = floor(log(s(x)))

For each node x, we'll keep r(x) tokens on that node.  (Alternatively,
the potential function will just be the sums of the ranks of all the
nodes in the tree.)

Here's an example to illustrate this: Here's a tree, labeled with sizes
on the left and ranks on the right.

               9 o                           3 o
                /                             /
             8 o                           3 o
              / \                           / \
           6 o   o 1                     2 o   o 0
            / \	                          / \
         2 o   o 3                     1 o   o 1
          /   /	                        /   /
       1 o   o 2                     0 o   o 1
              \	                            \
               o 1                           o 0

Notes about this potential:

    (1) Doing a rotation between a pair of nodes x and y only effects
        the ranks of the nodes x and y, and no other nodes in the tree.
        Furthermore, if y was the root before the rotation, then the
        rank of y before the rotation equals the rank of x after the
        rotation.

    (2) Assuming all the weights are 1, the potential of a balanced tree
        is O(n), and the potential of a long chain (most unbalanced
        tree) is O(n log n).

    (3) In the banker's view of amortized analysis, we can
        think of having r(x) tokens on node x.

Access lemma:  The number of splaying steps done when splaying
node x in a tree with root t is at most 3(r(t)-r(x))+1.

Proof:

As we do the work, we must pay one token for each splay step we do.
Furthermore we must make sure that there are always r(x) tokens on node
x as we do our restructuring.  We are going to allocate 3(r(t) - r(x)) +1
tokens to do the splay.  Our job in the proof is to show that this is
enough.

First we need the following observation about ranks, called the Rank
Rule.  Suppose that two siblings have the same rank, r.  Then the parent
has rank at least r+1.  This is because if the rank is r, then the size
is at least 2^r.  If both siblings have size at least 2^r, then the
total size is at least 2^(r+1) and we conclude that the rank is at least
r+1.  We can represent this with the following diagram:

                 x
                / \       Then x >= r+1
               r   r

Conversly, suppose we find a situation where a node and its parent have
the same rank, r.  Then the other sibling of the node must have rank < r.
So if we have three nodes configured as follows, with these ranks:

                 r
                / \       Then x < r
               x   r

Now we can go back to proving the lemma.  The approach we take is to
show that the 3(r'(x) - r(x)) tokens are sufficient to pay for the a
zig-zag or a zig-zig steps.  And that 3(r'(x) - r(x)) +1 is sufficient
to pay for the zig step.  (Here r'() represents the rank function after
the step, and r() represents the rank function before the step.)

When we sum these costs to compute the amortized cost for the entire
splay operation, they telescope to:

            3(r(t) - r(x)) +1.

Note that the +1 comes from the zig step, which can happen only once.

Here's an example, the labels are ranks:

           2 o                     2 x
            /                       / \
         2 o                     0 o   o 2
          /                           /
       2 o        splay(x)         2 o
        /        ==========>        / \
     2 x                         1 o   o 0
      / \                         /
   0 o   o 1                   0 o
        /
       o 0

-----------------            ----------------
Total:  9                           7

We allocated:                   3(2-2)+1 = 1
extra tokens from restructuring:   9-7   = 2
                                      ------
                                           3

There are 2 splay steps.  So 3 > 2, and we have enough.

It remains to show these bounds for the individual steps.  There are
three cases, one for each of the types of splay steps.

Zig:
               r o               o r
                /       ==>       \
             a o                   o b <= r

The actual cost is 1, so we spend one token on this.
We take the tokens on a and augment them with another
r-a and put them on b.  Thus the total number of tokens
needed is 1+r-a.  This is at most 1+3(r-a).

Zig-zag:   We'll split it into 2 cases:

  Case 1: The rank does not increase between the starting
          node and ending node of the step.

                r o                    o r
                 /                    / \
              r o         ===>     a o   o b
                 \
                r o

  By the Rank Rule, one of a or b must be < r, so
  one token is released from the data structure.
  we use this to pay for the work.

  Thus, our allocation of 3(r-r) = 0 is sufficient
  to pay for this.

  Case2: (The rank does increase)

                r o                  o r
                 /                  / \
              b o         ===>   c o   o d
                 \
                a o

  The tokens on c can be supplied from those on b.
  (There are enough there cause b >= c.)

  Note that r-a > 0.  So:

    use r-a (which is at least 1) to pay for the work
    use r-a to augment the tokens on a to make enough for d

  Summing these two gives:  2(r-a).  But 2(r-a) <= 3(r-a), which
  completes the zig-zag case.

Zig-zig:  Again, we split into two cases.

  Case 1: The rank does not increase between the starting node and the
          ending node of the step.

         r o                r o                 o r
          /                  / \                 \
       r o      =====>    r o   o d   ====>       o c <= r
        /                                          \
     r o              (d<r by rank rule)            o d < r

  As in the zig-zag case, we use the token gained because
  d < r to pay for the step.  Thus we have 3(r-r)=0 tokens
  and we need 0.

  Case 2: The rank increases during the step.

           r o                               o r
            /                                 \
         b o          ============>            o c <= r
          /                                     \
       a o                                       o d <= r

  use r-a (which is at least 1) to pay for the work
  use r-a to boost the tokens on a to cover those needed for d
  use r-a to boost the tokens on b to cover those needed for c

  Summing these gives 3(r-a), which completes the analyzis
  of the zig-zig case.

This completes the proof of the access lemma.  QED

Balance Theorem:  A sequence of m splays in a tree of n nodes takes time
O(m log(n) + n log(n)).

Proof:  We apply the access lemma with all the weights equal to 1.  For
a given splay, r(t) <= log(n), and r(x) >= 0.  So the amortized cost of
the splay is at most:

              3 log(n) + 1

We now switching to the world of potentials (potential = total tokens in
the tree).  To bound the cost of the sequence we add this amount for
each splay, then add the initial minus the final potential.  The initial
potential is at most n log(n), and the final potential is at least 0.
This gives a bound of:

         m (3 log(n) + 1) + n log(n) = O(m log(n) + n log(n))

Q.E.D.

In the nect lecture, we'll prove  more interesting results by using
non-uniform weights.

Comments are closed.