底层:跳跃链表skiplist

本文深入解析跳表数据结构的原理,包括节点结构、初始化、插入、删除和查找算法。并通过Java实现跳表,详细说明了跳表的迭代器功能。同时,介绍了Redis中跳表的应用场景,如组织score和调整元素权重的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

底层:跳跃链表skiplist

跳表

1742600-20190915130408615-884287656.png

每个node的结构:含键和值,还有一个由多个node组成的list,就是本node在不同层指向下一个node的指针。

每次初始化时初始化最初的节点,也就是最左边的节点,它的高度就是跳表最大高度,初始每个位置指针指向null。

插入元素时首先判断该元素在集合中是否存在,从最初节点的最高位置开始寻找,向前的指针键值比要找的值大就向下移动,小就向前移动。如果不存在就插入,首先用随机函数每次随机一个数0或1,出现一次1就停止,出现0的个数就是层数。然后从初始节点的对应层开始,如果前一个节点值大,就生成一个node,令初始节点层指针指向node,node向前指向本来前面的值,如果前一个节点值小就向下移动然后继续判断,直到走完第一层,对应节点的所有向前指针都已经创建完毕了。

删除时只需要找到该节点该层谁指向它,把指针指向node前面的node即可。

java实现的跳表

跳表的node:

class SkipListNode{
        public Integer value;
        //指向下一个node,如果本node是5层,那么这个list大小为5,分别指向1-5层的下一个node
        public ArrayList<SkipListNode> nextNodes;
        public SkipListNode(Integer value) {
            this.value = value;
            nextNodes = new ArrayList<MySkipList.SkipListNode>();
        }
}

跳表list的字段和构造方法:

class SkipList{
        //最小值,跳表的起始节点,它的高度和跳表中node的最大高度一致
        private SkipListNode head;
        
        //跳表达到的最大高度
        private int maxLevel;
        
        //跳表中元素的个数
        private int size;
        
        //跳表决定高度的概率,以0.5的概率产生0,如果是1就晋升一层
        private static final double PROBABILITY = 0.5;
        
        public SkipList() {
            size = 0;
            maxLevel = 0;
            head = new SkipListNode(null);
            head.nextNodes.add(null);
        }
}

跳表的add方法:

//添加元素
public void add(Integer newValue) {

    //如果不包含这个元素,就插入跳表
    if(!contains(newValue)) {
        //随机确定新的层高
        size++;
        int level = 0;
        while(Math.random() < PROBABILITY) {
            level++;
        }
        while(level > maxLevel) {
            head.nextNodes.add(null);
            maxLevel++;
        }
        //建立node,确定head为查找时的起始node
        SkipListNode newNode = new SkipListNode(newValue);
        SkipListNode current = head;
        //遍历的时候每次要么向前走,要么向下走,按照层数遍历
        //每一层都要建立新node,然后连接前后指针
        do {
            //获得这一层里最后一个大于newValue的node
            current = findNext(newValue, current, level);

            //生成newNode其中一个向前的指针(指向更大的node),每次都从0添加
            //这样最后如果一共有5层,相当于先添加第五层然后第四层。。
            //每个指针指向的就是current的前一个节点
            //连接从node到前
            newNode.nextNodes.add(0, current.nextNodes.get(level));

            //使newNode和它后面的节点产生联系,将后面节点的向前指针连接到newNode
            //连接从node到后(指向更小的node)
            current.nextNodes.set(level, newNode);
        }while(level-- > 0);
    }
}

查找方法findNext:

//找到要找的目标值e在这一层level里刚刚大于e的node,current是寻找的起始位置
private SkipListNode findNext(Integer e, SkipListNode current, int level) {
    //找到current节点在当前层的前一个节点
    SkipListNode next = current.nextNodes.get(level);
    while(next != null) {
        Integer value = next.value;
        //如果本节点是8,前一个节点是10,要找的值是9就会出现这种情况
        //此时直接返回,进入下一层
        if(e < value) {
            break;
        }
        //如果本节点是8,前一个节点是10,要找的值是12就会继续向前找
        //更新current和next

        //每次如果前一个节点比要找的值小就前进,否则找下一层
        current = next;
        next = current.nextNodes.get(level);
    }
    return current;
}

contains方法:

public boolean contains(Integer value) {
    SkipListNode node = find(value);
    return node != null && node.value != null && node.value == value;
}

//找到e对应的节点
private SkipListNode find(Integer e) {
    return find(e, head, maxLevel);
}

delete方法:

public void delete(Integer deleteValue) {
    if(contains(deleteValue)) {
        SkipListNode deleteNode = find(deleteValue);
        size--;
        int level = maxLevel;
        SkipListNode current = head;
        do {
            //找到刚刚比deleteNode稍大一点的node,然后将该node的各向前指针修正为
            //原来deleteNode向前指针的指向,相当于删除了deleteNode
            current = findNext(deleteNode.value, current, level);
            if(deleteNode.nextNodes.size() > level) {
                current.nextNodes.set(level, deleteNode.nextNodes.get(level));
            }
        }while(level-- > 0);
    }
}

跳表的迭代器(遍历结果一定是从小到大的):

class SkipListIterator implements Iterator<Integer>{

        SkipList list;
        SkipListNode current;
        
        public SkipListIterator(SkipList list) {
            this.list = list;
            this.current = list.getHead();
        }
        
        @Override
        public boolean hasNext() {
            // TODO Auto-generated method stub
            return current.nextNodes.get(0) != null;
        }

        @Override
        public Integer next() {
            // TODO Auto-generated method stub
            current = current.nextNodes.get(0);
            return current.value;
        }
        
}

redis中的跳表

在组织score时redis采用跳表,而从member到score的映射redis使用字典来存储。

在redis中每一层的晋升概率是25%,它是一种更扁平化的跳表,在单个层上遍历的节点个数就会稍多一些。

在调整元素权重时,redis采用对该节点先删后加的方式来进行。

如果权重都相同redis还会比较value值,使redis中的跳表有序。

redis计算元素排名rank时,是对跳表功能的一种加强,对于每个元素都有它的字段rank值,跳表在变化时会更新这个值。

转载于:https://www.cnblogs.com/shizhuoping/p/11521860.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值