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