• 跳表的设计与应用场景


    skiplist一般采用多层链表来实现,不同层之间依靠down指针来链接。
    在这里插入图片描述

    设计参考:跳表设计
    设计思路:跳表的设计主要分为 查询、添加和删除三个部分。
    查询:先从最上层的头节点开始查询,若当前节点值小于目标值,往右走,一直走到下一个节点(next指针)的值大于目标值的位置,然后向下寻找,直到找完最底层的链表,这个过程中若能找到值与目标值相等的节点,返回,没有则返回false.
    新增
    由于最底层链表存储了原始链表,所以新增元素必然出现在最底层,至于索引层,如何判断该元素是否应该出现在当前索引层,这个问题对整体复杂度有影响,比较复杂,这里先抛出一个简单的想法:当最底层插入完成后,每往上移动一层,当前层以1/2的概率决定是否在当前层插入。
    删除
    删除思路是比较直接的,从顶层开始查找,找到目标值,在当前层链表删除此节点即可,一直遍历整个链表。

    代码实现

    import java.util.Random;
    import java.util.Stack;
    
    public class Skiplist {
    
        class Node {
            int val;
            Node next, down;
            public Node(int val, Node next, Node down) {
                this.val = val;
                this.next = next;
                this.down = down;
            }
        }
    
        private Node head = new Node(-1, null, null);
    
        Random rand = new Random();
    
        public Skiplist() {
    
        }
    
        public boolean search(int target) {
            Node cur = head;
            while (cur != null) {
                while (cur.next != null && cur.next.val < target) {
                    cur = cur.next;
                }
                if (cur.next != null && cur.next.val == target) return true;
                cur = cur.down;
            }
            return false;
        }
    
        public void add(int num) {
            Stack<Node> stack = new Stack<>();
            Node cur = head;
            while (cur != null) {
                while (cur.next != null && cur.next.val < num) {
                    cur = cur.next;
                }
                stack.push(cur);
                cur = cur.down;
            }
            boolean insert = true;
            Node down = null;
            while (insert && !stack.isEmpty()) {
                cur = stack.pop();
                cur.next = new Node(num, cur.next, down);
                down = cur.next;
                insert = rand.nextDouble() < 0.5;
            }
            if (insert) head = new Node(-1, null, head);
        }
    
        public boolean erase(int num) {
            Node cur = head;
            boolean found = false;
            while (cur != null) {
                while (cur.next != null && cur.next.val < num) {
                    cur = cur.next;
                }
                if (cur.next != null && cur.next.val == num) {
                    found = true;
                    cur.next = cur.next.next;
                }
                cur = cur.down;
            }
            return found;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    跳表主要应用是在 Redis 缓存数据库中来实现有序集合(Sorted Set)

    相关问题:Redis为什么使用跳跃表来实现有序集合(Sorted Set)而不是红黑树或者平衡二叉树?

    1:skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做单个key的查找,不适宜做范围查找。所谓范围查找,指的是查找那些大小在指定的两个值之间的所有节点。在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。

    2:平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。

    3:从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。

    4:查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的。

    从算法实现难度上来比较,skiplist比平衡树要简单得多。

    参考文档:https://mp.weixin.qq.com/s?__biz=MzA4NTg1MjM0Mg==&mid=2657261425&idx=1&sn=d840079ea35875a8c8e02d9b3e44cf95&scene=21#wechat_redirect

  • 相关阅读:
    与国自然焦虑对线的感悟
    JDK核心JAVA源码解析(8) - 自动封箱拆箱与效率的思考
    java集合专题_集合体系介绍_集合遍历(1)
    小白都能看懂得Xxl-job安装教程
    MapReduce分区、排序、Combiner
    Webpack 5的十大提升配置技巧
    多线程进阶1 --- 锁策略+CAS+synchronized原理
    PDF图片提取的方法有什么?这个方法1分钟提取完毕
    企业使用有线和5G主备双链路上网配置案例
    python使用unittest进行单元测试
  • 原文地址:https://blog.csdn.net/zjshuster/article/details/125529134