Topic 5 - Splay Trees

We will see another simple binary search tree, the splay tree.

Principle

The objective of the splay trees is to guarantee that any sequences of \(M\) operations take at most \(O(M \log N)\) time.

Important

It does not guarantee that all operations are \(O(\log N)\).

With this data structure, we are talking about amortized running time.

When a sequence of \(M\) operations have a total worst-case running time of \(O(M f(N))\), then the amortized running time is \(O(f(N))\).

To simplify, splay tree operations have an average running time of \(O(\log N)\).

How does it work?

The basic idea is very simple:

  • After a node is accessed, the node is pushed to the root using AVL tree rotations.

  • The AVL rotations will balance the tree, making future access faster.

Note

In practice, when a node is accessed, it is likely it will be accessed again.

Ok, but AVL trees are doing the same thing!

Yes, but…

Splay trees don’t require to maintain the height or balance information!

It simplifies the code and save space in memory.

A first simple idea

Now, we know that we need to apply rotations similarly to the AVL trees.

We could just apply simple rotation bottoms up!

Consider the following example. We want to find \(k_1\).

_images/first_idea_01.png

We can see the access path (dashed lined). So we need to rotate \(k_1\) and \(k_2\).

_images/first_idea_02.png

Then we rotate \(k_1\) and \(k_3\).

_images/first_idea_03.png

We continue with \(k_4\).

_images/first_idea_04.png

Finally we need a last rotation with \(k_5\).

_images/first_idea_05.png

Activity

  • Observe the tree after all the rotations.

  • Does it do what we want?

  • Is the tree balanced?

It is possible to prove that there is a sequence of \(M\) operations requiring \(\Omega(M\times N)\) time.

Splaying

Clearly, the previous strategy is not working as expected.

Splay trees are implementing the splaying strategy.

We are still rotating along the path to the root, but we are more selective hon the rotation type.

Let \(X\) be a non-root node on the access path.

  • If the parent of \(X\) is the root -> rotate \(X\) and the root.

  • Otherwise has both a parent \(P\) and a grandparent \(G\).

    • If \(X\) is an internal node -> this is the zig-zag case.

    • Otherwise -> this is the zig-zig case.

The zig-zag is a double rotation like with the AVL trees.

The following figure illustrates the zig-zag:

_images/FG_04_048.svg

The zig-zig is a single rotation.

The following figure illustrates the zig-zig:

_images/FG_04_049.svg

The idea is in the end very simple.

Activity

  • Take the previous example and apply the algorithm.

_images/first_idea_01.png
  • Draw the final graph

  • Is it balanced?

Important

A splay tree is not balanced and it is not the point!

_images/splay_03.png

Implementation

After seeing BST and AVL trees, the operation of the Splay trees should not be an issue.

Important

It is recommended to implement iterative functions to splay trees.

Structure

As we will have an iterative approach, a node needs to keep a pointer to its parent.

 1  private:
 2    struct SplayNode
 3    {
 4      Comparable  element;
 5      SplayNode   *left;
 6      SplayNode   *right;
 7      SplayNode   *parent;
 8
 9      SplayNode( const Comparable & ele, SplayNode *lt, SplayNode *rt, SplayNode *p)
10        : element{ ele }, left{ lt }, right{ rt }, parent{ p } { }
11
12    };

I am giving you the header file.

Inserting

The insertion is working as in AVL tree, but instead of balancing after inserting the node you splay.

You splay only once, just after inserting.

Activity

  • Implement the insertion function void insert( const Comparable & x );.

  • Consider the splay function already implemented: void splay( SplayNode * t );

Deletion

The idea for deleting a node is the following:

  • Finding the node.

  • Splay the node to put it at the root.

  • Remove the node, so it gives you two subtrees left and right.

  • Find the maximum element in the left subtree.

  • Splay it, now link the right subtree to the right element of the node (that should be empty)

Splay

We already discussed how the function is working.

Activity

  • Implement the function void splay( SplayNode * t ).