4.3

A `SkiplistList` implements the `List` interface using a skiplist
structure. In a `SkiplistList`, contains the elements of the
list in the order in which they appear in the list. As in a
`SkiplistSSet`, elements can be added, removed, and accessed in
time.

For this to be possible, we need a way to follow the search path for the th element in . The easiest way to do this is to define the notion of the length of an edge in some list, . We define the length of every edge in as 1. The length of an edge, , in , , is defined as the sum of the lengths of the edges below in . Equivalently, the length of is the number of edges in below . See Figure 4.5 for an example of a skiplist with the lengths of its edges shown. Since the edges of skiplists are stored in arrays, the lengths can be stored the same way:

class Node { T x; Node[] next; int[] length; @SuppressWarnings("unchecked") Node(T ix, int h) { x = ix; next = (Node[])Array.newInstance(Node.class, h+1); length = new int[h+1]; } int height() { return next.length - 1; } }

The useful property of this definition of length is that, if we are currently at a node that is at position in and we follow an edge of length , then we move to a node whose position, in , is . In this way, while following a search path, we can keep track of the position, , of the current node in . When at a node, , in , we go right if plus the length of the edge is less than . Otherwise, we go down into .

Node findPred(int i) { Node u = sentinel; int r = h; int j = -1; // index of the current node in list 0 while (r >= 0) { while (u.next[r] != null && j + u.length[r] < i) { j += u.length[r]; u = u.next[r]; } r--; } return u; }

T get(int i) { if (i < 0 || i > n-1) throw new IndexOutOfBoundsException(); return findPred(i).next[0].x; } T set(int i, T x) { if (i < 0 || i > n-1) throw new IndexOutOfBoundsException(); Node u = findPred(i).next[0]; T y = u.x; u.x = x; return y; }

Since the hardest part of the operations and is finding the th node in , these operations run in time.

Adding an element to a `SkiplistList` at a position,
, is fairly
simple. Unlike in a `SkiplistSSet`, we are sure that a new
node will actually be added, so we can do the addition at the same time
as we search for the new node's location. We first pick the height,
,
of the newly inserted node,
, and then follow the search path for
.
Any time the search path moves down from
with
, we
splice
into
. The only extra care needed is to ensure that
the lengths of edges are updated properly. See Figure 4.6.

Note that, each time the search path goes down at a node, , in , the length of the edge increases by one, since we are adding an element below that edge at position . Splicing the node between two nodes, and , works as shown in Figure 4.7. While following the search path we are already keeping track of the position, , of in . Therefore, we know that the length of the edge from to is . We can also deduce the length of the edge from to from the length, , of the edge from to . Therefore, we can splice in and update the lengths of the edges in constant time.

This sounds more complicated than it is, for the code is actually quite simple:

void add(int i, T x) { if (i < 0 || i > n) throw new IndexOutOfBoundsException(); Node w = new Node(x, pickHeight()); if (w.height() > h) h = w.height(); add(i, w); }

Node add(int i, Node w) { Node u = sentinel; int k = w.height(); int r = h; int j = -1; // index of u while (r >= 0) { while (u.next[r] != null && j+u.length[r] < i) { j += u.length[r]; u = u.next[r]; } u.length[r]++; // accounts for new node in list 0 if (r <= k) { w.next[r] = u.next[r]; u.next[r] = w; w.length[r] = u.length[r] - (i - j); u.length[r] = i - j; } r--; } n++; return u; }

By now, the implementation of
the
operation in a `SkiplistList` should be obvious. We follow the search path for the node at position
. Each time the search path takes a step down from a node,
, at level
we decrement the length of the edge leaving
at that level. We also check if
is the element of rank
and, if so, splice it out of the list at that level. An example is shown in Figure 4.8.

T remove(int i) { if (i < 0 || i > n-1) throw new IndexOutOfBoundsException(); T x = null; Node u = sentinel; int r = h; int j = -1; // index of node u while (r >= 0) { while (u.next[r] != null && j+u.length[r] < i) { j += u.length[r]; u = u.next[r]; } u.length[r]--; // for the node we are removing if (j + u.length[r] + 1 == i && u.next[r] != null) { x = u.next[r].x; u.length[r] += u.next[r].length[r]; u.next[r] = u.next[r].next[r]; if (u == sentinel && u.next[r] == null) h--; } r--; } n--; return x; }

The following theorem summarizes the performance of the `SkiplistList`
data structure:

opendatastructures.org