跳表

跳表是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。

https://leetcode.cn/problems/design-skiplist/description/

算法步骤

查找过程中,每比较一次要么向下一层,要么到本层的右侧节点。每个节点在进行插入的时候,它的层数是由随机函数randomLevel()计算出来的,随机的计算不依赖于其它的节点,每次插入过程都是完全独立的。
位于第 i 层的节点有 p 的概率出现在第 i+1 层。
redis中,p = 1/4,MaxLevel = 32。

image

1. 先随机出来一个层数,new要插入的节点,先叫做插入节点newNode;
2. 根据跳表实际的总层数从上往下分析,要插入一个节点newNode时,先找到节点在该层的位置:因为是链表,所以需要一个节点node,满足插入插入节点newNode的值刚好不大于node的下一个节点值,当然,如果node的下个节点为空,那么也是满足的。
3. 确定插入节点newNode在该层的位置后,先判断下newNode的随机层数是否小于当前跳表的总层数,如果是,则用链表的插入方法将newNode插入即可。
4. 如此循环,直到最底层插入newNode完毕。
5. 循环完毕后,还需要判断下newNode随机出来的层数是否比跳表的实际层数还要大,如果是,直接将超过实际层数的跳表的头节点指向newNode即可,该跳表的实际层数也就变为newNode的随机层数了。

image

代码

class Skiplist {
    //最大层数        
    private static int DEFAULT_MAX_LEVEL = 32;
    //随机层数概率,也就是随机出的层数,在第1层以上(不包括第一层)的概率,
    //层数不超过maxLevel,层数的起始号为1
    private static double DEFAULT_P_FACTOR = 0.25;

    Node head = new Node(null,DEFAULT_MAX_LEVEL); //头节点

    int currentLevel = 1; //表示当前nodes的实际层数,它从1开始


    public Skiplist() {}

    public boolean search(int target) {
        Node searchNode = head;
        for (int i = currentLevel-1; i >=0; i--) {
            searchNode = findClosest(searchNode, i, target);
            if (searchNode.next[i]!=null && searchNode.next[i].value == target){
                return true;
            }
        }
        return false;
    }


    public void add(int num) {
        int level = randomLevel();
        Node updateNode = head;
        Node newNode = new Node(num,level);
        // 计算出当前num 索引的实际层数,从该层开始添加索引
        for (int i = currentLevel-1; i>=0; i--) {
            //找到本层最近离num最近的list
            updateNode = findClosest(updateNode,i,num);
            if (i<level){
                if (updateNode.next[i]==null){
                    updateNode.next[i] = newNode;
                }else{
                    Node temp = updateNode.next[i];
                    updateNode.next[i] = newNode;
                    newNode.next[i] = temp;
                }
            }
        }
        if (level > currentLevel){ //如果随机出来的层数比当前的层数还大,那么超过currentLevel的head 直接指向newNode
            for (int i = currentLevel; i < level; i++) {
                head.next[i] = newNode;
            }
            currentLevel = level;
        }

    }

    public boolean erase(int num) {
        boolean flag = false;
        Node searchNode = head;
        for (int i = currentLevel-1; i >=0; i--) {
            searchNode = findClosest(searchNode, i, num);
            if (searchNode.next[i]!=null && searchNode.next[i].value == num){
                //找到该层中该节点
                searchNode.next[i] = searchNode.next[i].next[i];
                flag = true;
                continue;
            }
        }
        return flag;
    }

    //找到level层, value 大于node 的节点
    private Node findClosest(Node node,int levelIndex,int value){
        while ((node.next[levelIndex])!=null && value >node.next[levelIndex].value){
            node = node.next[levelIndex];
        }
        return node;
    }


    //随机一个层数
    private static int randomLevel(){
        int level = 1;
        while (Math.random()<DEFAULT_P_FACTOR && level<DEFAULT_MAX_LEVEL){
            level ++ ;
        }
        return level;
    }


    class Node{
        Integer value;//节点值
        Node[] next;// 节点在不同层的下一个节点

        public Node(Integer value,int size) {// size表示当前节点在跳表中索引几层
            this.value = value;
            this.next = new Node[size];
        }
    }
}

参考资料

https://blog.csdn.net/qq_40707462/article/details/122973441

posted on 2026-04-15 08:57  王景迁  阅读(11)  评论(0)    收藏  举报

导航