Lam NguyenLibrary

Library · Databases · storage structures

Skip List

/*
HW: show that memory references of a node is consistent
*/
#pragma once
#include <bits/stdc++.h>
#include "utils/hash.h"
using namespace std;

class SkipList {
private:
    struct Node {
        int key;
        int value;
        // next[i] = next node at level i
        vector<Node*> next;
        Node(int k, int v, int lvl) : key(k), value(v) {
            next.resize(lvl+1, nullptr);
        }
    };

    int toss() { 
        static std::mt19937 rng(std::random_device{}());
        int lvl = 0;
        while ((rng() & 1u) && lvl < MAX_LEVEL - 1) {
            lvl++;
        }
        return lvl;
    }

    const int MAX_LEVEL = 16;

    Node* head;
    int curr_mx_lvl;

public:
    SkipList() {
        head = new Node(-1, -1, MAX_LEVEL);
        curr_mx_lvl = 0;
    }

    int find(int key) {
        Node* curr = head;

        // each i is node moving down
        for (int i = curr_mx_lvl; i >= 0; i--) {
            // move right while has next node and next node has key < current search key
            while (curr->next[i] != nullptr && curr->next[i]->key < key) {
                curr = curr->next[i];
            }
        }

        // we are at level 0
        // since condition is curr->next[i]->key < key
        // we can safely move curr to next
        curr = curr->next[0];

        if (curr != nullptr && curr->key == key) {
            return curr->value;
        }

        return -1;
    }

    /* 
        left neighbor node for every level
            e.g. (k, v) with k < key
        motivation behind this is each node, even though it is the same curr
            has different address in the memory, so updating using the last curr
            will result in pointers being misplaced everywhere
        also curr is correct if we update only level 0

        if current node is higher than current max level, its left is head

        https://www.cs.cmu.edu/~ckingsf/bioinfo-lectures/skiplists.pdf (slides 20)
            each node is 1 address referenced many times with the pointers
                1 big node, instead of fragmented copies of a node
            we will be adding the node to the levels as high as max of 2 neighbors
                or we have reached maximum level of the node
    */
    void insert(int key, int value) {
        Node* curr = head;

        vector<Node*> left(MAX_LEVEL, nullptr);

        for (int i = curr_mx_lvl; i >= 0; i--) {
            while (curr->next[i] != nullptr && curr->next[i]->key < key) {
                curr = curr->next[i];
            }
            left[i] = curr;
        }

        curr = curr->next[0];

        if (curr != nullptr && curr->key == key) {
            curr->value = value;
            // We don't propagate changes up because SkipList
            // only update in a single place (leaf node)
            return;
        }

        int node_lvl = toss();

        if (node_lvl > curr_mx_lvl) {
            for (int i = curr_mx_lvl+1; i <= node_lvl; i++) {
                left[i] = head;
            }
            curr_mx_lvl = node_lvl;
        }

        Node* newNode = new Node(key, value, node_lvl);

        for (int i = 0; i <= node_lvl; i++) {
            Node* nxt = left[i]->next[i];
            left[i]->next[i] = newNode;
            newNode->next[i] = nxt;
        }
    }

    int remove(int key) {
        Node* curr = head;

        vector<Node*> left(MAX_LEVEL, nullptr);

        for (int i = curr_mx_lvl; i >= 0; i--) {
            while (curr->next[i] != nullptr && curr->next[i]->key < key) {
                curr = curr->next[i];
            }
            left[i] = curr;
        }

        Node* target = curr->next[0];
        if (target == nullptr || target->key != key) {
            return 0;
        }

        for (int i = 0; i <= curr_mx_lvl; i++) {
            if (left[i]->next[i] != target) {
                break;
            }
            left[i]->next[i] = target->next[i];
        }

        delete target;
        return 1;
    }
};
import random

LEVELS = 16

class Node:
    def __init__(self, value, level):
        self.value = value
        self.next = [None] * level # next[i] = next node at level i

class Skiplist:

    def __init__(self):
        self.head = Node(-1, LEVELS) # sentinel node

    def _toss(self):
        level = 1
        while random.random() < 0.5 and level < LEVELS:
            level += 1
        return level

    # last node with value < x at every level
    def _left(self, x):
        left, cur = [None] * LEVELS, self.head
        for i in reversed(range(LEVELS)):
            while cur.next[i] and cur.next[i].value < x:
                cur = cur.next[i]
            left[i] = cur
        return left

    def search(self, target: int) -> bool:
        node = self._left(target)[0].next[0]
        return node is not None and node.value == target

    def add(self, num: int) -> None:
        left = self._left(num)
        node = Node(num, self._toss())
        for i in range(len(node.next)):
            node.next[i] = left[i].next[i]
            left[i].next[i] = node

    def erase(self, num: int) -> bool:
        left = self._left(num)
        node = left[0].next[0]
        if node is None or node.value != num:
            return False
        for i in range(len(node.next)):
            if left[i].next[i] is node:
                left[i].next[i] = node.next[i]
        return True

# Your Skiplist object will be instantiated and called as such:
# obj = Skiplist()
# param_1 = obj.search(target)
# obj.add(num)
# param_3 = obj.erase(num)