In the world of computer science and data management, there are numerous ways to organize and access data efficiently. One such innovative data structure is the skip list. This blog aims to provide a comprehensive yet engaging primer on skip lists, covering their structure, operations, and analysis. By the end of this read, you'll have a solid understanding of skip lists and their significance in computer science.

What is a Skip List?

A skip list is an advanced data structure that allows fast search, insertion, and deletion operations. It is essentially a hierarchy of linked lists where each level is a subset of the level below it. This hierarchical organization enables higher-level linked lists to "skip" many elements, making search operations more efficient.

Structure of a Skip List

Operations on a Skip List

Skip lists support three primary operations: search, insertion, and deletion. Let's delve into each of these operations with a step-by-step breakdown.

The search operation in a skip list begins at the topmost level and progresses downwards until the desired element is found or confirmed absent.

Algorithm:

skipList::search(int k) {
    // Get the list of predecessors of key k
    std::stack<Node*> P = getPredecessors(k);

    // Get the predecessor of k in the bottom-level list (L0)
    Node* p0 = P.top();

    // Check if the key after p0 is k
    if (p0->after->key == k) {
        // If found, return the key-value pair at p0->after
        return p0->after->kvp;
    } else {
        // If not found, return "not found"
        return "not found";
    }
}

Get Predecessors:

Get predecessors takes in a node with key k, and returns a stack containing every predecessor of the given node on each list of the skip list.

std::stack<Node*> skipList::getPredecessors(int k) {
    // Start at the root node
    Node* p = root;

    // Initialize a stack of nodes, initially containing p
    std::stack<Node*> P;
    P.push(p);

    // Traverse downwards until reaching the bottom level
    while (p->below != NULL) {
        // Move to the node below
        p = p->below;
        // Traverse rightwards until the key after p is not less than k
        while (p->after->key < k) {
            p = p->after;
        }
        // Push the current node onto the stack
        P.push(p);
    }

    // Return the stack of predecessors
    return P;
}

2. Insertion

Inserting an element involves determining a random height for the node and then placing it appropriately across different levels.

Algorithm:

void skipList::insert(int k, int v) {
    // Determine the random tower height
    int i;
    for (i = 0; random(2) == 1; i++);

    // Ensure the skip list has enough levels
    int h = 0;
    Node* p = root.below;
    while (p != NULL) {
        // Check if we need to increase the skip list height
        while (i >= h) {
            // Step 4: Create a new sentinel-only list and link it in below the topmost list
            createNewSentinelList();
            h++;
        }
        // Move to the node below
        p = p->below;
        h++;
    }

    // Get the list of predecessors for key k
    std::stack<Node*> P = getPredecessors(k);

    // Insert (k, v) in the bottom-level list (L0)
    Node* p = P.top();
    P.pop();
    Node* zbelow = new Node(k, v);
    zbelow->after = p->after;
    p->after = zbelow;

    // Insert k in levels L1 to Li
    while (i > 0) {
        p = P.top();
        P.pop();
        Node* z = new Node(k);
        z->after = p->after;
        p->after = z;
        z->below = zbelow;
        zbelow = z;
        i--;
    }
}

3. Deletion

Deletion involves finding all instances of the node across different levels and removing them.

Algorithm:

skipList::delete(k)
1. P ← get-predecessors(k)
2. while P is non-empty
3. p ← P.pop() // predecessor of k in some list
4. if p.after.key = k
5. p.after ← p.after.after
6. else break // no more copies of k

Analysis of Skip Lists

Since a skiplist is randomly generated from the insert method, we analyze the expected space and height from the skip list (the worst case is that insert doesn't terminate due to creating a node with infinite height).