跳表SkipList:可二分查找的有序链表,实现有序表,思想先进,操作复杂度O(logn)

跳表SkipList:可二分查找的有序链表,实现有序表,思想先进,操作复杂度O(logn)

提示:这段时间,讲有序表、跳表的底层数据结构,平衡搜索二叉树:AVL树,SB树,红黑树
基础知识:
【1】求二叉树中节点x的后继节点和前驱结点
【2】二叉树,二叉树的归先序遍历,中序遍历,后序遍历,递归和非递归实现
【3】平衡搜索二叉树BST底层的增删改查原理,左旋右旋的目的
【4】有序表TreeMap/TreeSet底层实现:AVL树、傻逼树SBT、红黑树RBT、跳表SkipMap失衡类型
【5】傻逼树SBT:Size Balanced Tree的实现原理,增删改查,调平衡


跳表的思想先进,比搜索二叉树牛,比BST简单

跳表是有序链表

跳表的节点:NodeSkipList,多指针,随意增减

它的节点,并不像二叉树那样,只有l和r指针,
跳表的节点NodeSkipList是有多个指针,这个指针的个数还是不固定,你想增加,就增加,你想减少就减少!

通过概率p来决定它一个node究竟有多少指针,统统放在nextNodes的列表(java中的ArrayList列表,就能自由新增add,一层一层的,get拿其中的i那层)中,一层一层堆好。
在这里插入图片描述
节点的key是K类型,可比的
节点有值value

当然节点最少有一条向外的指针指向null的,这个0层永远存在的

为了我们方便查找key是否存在?咱们封装2个基本方法

isKeyLess(otherKey)

调用是这样的key.isKeyLess(otherKey)
当key<otherKey时,返回true
当key是null自然也就满足上述条件得

注意:如果otherKey是null,实际上是找到了尾部的null,我们认为key比尾部小,key是要接到尾部的。
在这里插入图片描述

isKeyEqual(otherKey)

调用是这样的key.isKeyEqual(otherKey)
当key=otherKey时,返回true

注意:如果key和otherKey其中是null,没法比,返回false
如果都是null,算上相等吧
如果都不空,就判断相等与否?

综合节点的类就是:

    //节点
    public static class NodeSL<K extends Comparable<K>, V>{
        //K是可比较的
        public K key;
        public V value;
        public ArrayList<NodeSL<K, V>> nextNodes;//一堆一层一层的指针

        public NodeSL(K k, V v){
            //初始化
            key = k;
            value = v;
            nextNodes = new ArrayList<>();
        }

        //封装俩方法:
        //## isKeyLess(otherKey)
        //调用是这样的key.isKeyLess(otherKey)
        //**当key<otherKey时,返回true**
        public boolean isKeyLess(K otherKey){
            if (otherKey == null) return false;//尾部null算大

            return otherKey != null && (key == null || key.compareTo(otherKey) < 0);
        }
        
        //## isKeyEqual(otherKey)
        //调用是这样的key.isKeyEqual(otherKey)
        //**当key=otherKey时,返回true**
        public boolean isKeyEqual(K otherKey){
            if (key == null && otherKey == null) return true;
            else if (key != null && otherKey != null) return key.compareTo(otherKey) == 0;
            else return false;
        }
    }

跳表SkipListReview类结构:root挂一堆有序的链表,root的key=null,最小

跳表SkipListReview类

内部最左有一个节点root,它的key是null,表示本表中最小的key,
今后增加的所有key都比跳表最左root的key=null都要大【就这么认为的】
这就是为啥节点中isKeyLess方法的key=null时认为null最小了

还有常量PROBABLITY=0.5,随机分界线
跳表总节点个数N,size,最开始没有就是0个(不同的key)
跳表中的root节点的nextNodes的0层永远存在的,maxLevel=0;
在这里插入图片描述

    //跳表类
    public static class SkipListMap<K extends Comparable<K>, V>{
        //成员变量
        private static final double PROBABLITY = 0.5;
        public NodeSL<K, V> head;//头结点,最左边那个
        public int size;//整体节点个数
        public int maxLevel;//跳表中root的最高层数

        //构造函数
        public SkipListMap(){
            size = 0;
            head = new NodeSL<>(null, null);//这个key是全局最小
            head.nextNodes.add(null);至少1层是有的,最底层先加null
            maxLevel = 0;//至少1层是有的,最底层
        }

        //成员方法
    }

跳表新增节点

新增节点有几层呢?看概率p<=0.5的话,持续加层,p>0.5停止

至少节点有一个0层,让它指向null
如下图黑色指针
在这里插入图片描述
然后
看概率p<=0.5的话,持续加层,比如连续两次得到绿色,粉色数字,可以新增
一旦遇到一个p>0.5停止加层,比如遇到橘色数字,那不行了,停止,从此新节点就3层了。

如果root的层数maxLevel比新增节点的层数少,那root还需要追加,与新增节点的层数一样。
比如下图中,root最开始就一层maxLevel=1,而新来的节点3,它p出了3层,所以maxLevel需要增加到3层,即0 1 2,maxLevel=3
在这里插入图片描述
怎么挂接?
(1)从root的最高层maxLevel开始,往右找,最晚的<=3的节点last有吗?
(2)没有?让root直接连接3节点,然后去(4)
(3)有的话,让那个last的最高层连接3节点【现在先不管】
(4)继续在root下一层,不断往下每一层都要去搜索,往右找,最晚的<=3的节点last有吗?去(2)或者(3)
(5)直到root最后一层0层,都连接接好3节点。
目前只来了3节点,就新增如上图所示的情况

不妨设,现在来了新节点:5节点
(0)按概率p出数,决定新节点5有多少层?不妨设5节点的nextNodes只有2层,因为root足够,root的maxLevel不管。【看下图5节点,nextNodes有2个null】
(1)从root的最高层maxLevel=2开始,往右找,最晚的<=5的节点last有吗?last=3节点【看下图last】
由于目前last=3节点有3层,但是新节点newNode没有那么多层,在3的高层右找不能再右了,开始往下找!! 【此刻再往右试null了,不能再右了】
所以在last内部,往下走!!去level=1层往右找,最晚的<=5的节点last有吗?
(2)没有?让last=3在1层的指针,直接连接新节点newNode=5,然后去(4)
(3)有的话,【现在先不管】
(4)继续在last=3的下一层,level=0层,不断往下每一层都要去搜索,往右找,最晚的<=5的节点last有吗?去(2)或者(3)
(5)直到last=3最后一层0层,都连接接好5节点。【下图都连好了】
在这里插入图片描述
不妨设,现在来了新节点:2节点
(0)按概率p出数,决定新节点2有多少层?不妨设2节点的nextNodes只有1层,因为root足够,root的maxLevel不管。【看下图2节点,nextNodes有1个null】
(1)从root的最高层maxLevel=2开始,往右找,最晚的<=2的节点last有吗?
没有,在level=2的高层右找不能再右了,开始往下找!! 【此刻再往右试null了,不能再右了】
所以在root内部,往下走!!去level=1层往右找,最晚的<=2的节点last有吗?
没有,在level=1的高层右找不能再右了,开始往下找!! 【此刻再往右试null了,不能再右了】
所以在root内部,往下走!!去level=0层往右找,最晚的<=2的节点last有吗?有去(3)
(3)有的话,last其实现在是root,让2节点插入root和3节点之间,完成【下图都连好了】
在这里插入图片描述
这就是添加的过程,
(0)就是先搞p决定新来节点的层数,同时根据新节点层数追加root最高层maxLevel
(1)每次怎么连呢?从root最高层,不断往右找,当右到不能在右了,就在当地节点的下一层继续找,找到最右的<=key的节点last,让last连接新节点【可能是直接连,可能是插入】
(2)然后继续在这个last下一层去往右找,连接好新节点。直到这个last的0层也连号新节点,完成任务。

这个过程,由于p是随机的,0层必须存在,N个节点,至少0层一定全部连接的
1层的话,可能p会缩短,有N/2个节点是连接的
2层的话,可能p继续缩,有N/4个节点是连接的
……
直到最高层N-1层,可能p继续缩,与N/2的幂次幂个节点是连接的,连接的节点数目会越来越少

这么做的好处是啥呢???


跳表查询节点key是否存在?二分查找,o(log(n))速度快

便于快速查找key!!!
便于快速查找key!!!
便于快速查找key!!!

比如下图:一个跳表root,放了很多节点,底层连接多,高层连得少。
在这里插入图片描述
这样的话,你如果想找7节点,其实,你会秒杀,绕错下面那些层,从最高层开始查找,瞬间,你就找到了key=7的节点,速度就非常非快了

这个查找就是二分查找法,o(log(n))的速度,非常快的。

这就是跳表的牛逼之处了,比二叉树牛逼多了,简单,不用调平,无非就是新增层,然后将新节点,插入有序链表中,完事!!!

再举例你要查找3节点在哪,从root最高层maxLevel开始查,往右,右到不能再往右,在那往下一层查,继续往右,右到不能再往右,继续去下一层……
然后就绕过下面那些层,尽快找到了key=3的节点。
在这里插入图片描述
所以呢,新增节点的策略根据p决定每个点的层数随机,那么
由高层向底层,建立了一个索引,当高层越过一个节点,下面绕过一大批节点。

有点像快排算法的随机决定pivot,然后去partition,最后效果就是每次快排的规模大致缩小N/2
这样整体每次索引的规模逐渐减半,最后复杂度就是o(log(n))。


查找跳表的0层最右的小于key的节点cur

有了上面跳表加入的原理,为什么要设计随机概率增加节点层数的原理,我们就来封装一个很重要的函数

从root的最高层maxLevel处,开始查找并返回跳表的0层最右的<key的节点cur
在这里插入图片描述
看上图
(1)从cur=root开始,向右同一层中查找,只要cur<key继续往右查,往右到不能再往右了,停,此刻cur<key;
(2)在cur内部层数level–,继续循环(1),直到level=0的最右那个cur<key,返回cur。

其中每一个层level,都要向右不断找,实际就是一个单链表的查找!
上面的(1)很简单,就是在level层,不断往右查,往右到不能再往右了,停,此刻cur<key;
在这里插入图片描述

手撕代码来试试:每一个层level,都要向右不断找,不断往右查,往右到不能再往右了,停,此刻cur<key;

        //每一个层level,都要向右不断找,不断往右查,往右到不能再往右了,停,此刻cur<key;
        public NodeSL<K, V> findInLevelMostRightLessKey(NodeSL<K, V> cur, K key, int level){
            NodeSL<K, V> next = cur.nextNodes.get(level);//当前level层cur的下一个节点,方便右移
            while (next != null && cur.isKeyLess(next.key)){
                //只要cur比next小,继续右移,否则返回cur
                cur = next;
                next = cur.nextNodes.get(level);//往右移动
            }
            //一旦cur>=next
            return cur;
        }

手撕代码:从root的最高层maxLevel处,开始查找并返回跳表的0层最右的<key的节点cur

        //从root的最高层maxLevel处,开始查找并返回跳表的0层最右的<key的节点cur
        public NodeSL<K, V> mostRightLessKeyInSLMap(K key){
            if (key == null) return null;//null没法比
            //(1)从cur=root开始,
            NodeSL<K, V> cur = head;
            int level = maxLevel;//从root的最高层往右扫
            while (level >= 0){
                // (1)向右同一层中查找,只要cur<key继续往右查,往右到不能再往右了,停,此刻cur<key;
                cur = findInLevelMostRightLessKey(cur, key, level--);
                //(2)在cur内部层数level--,继续循环(1),直到level=0的最右那个cur<key,返回cur。
            }

            return cur;
        }

put(K key, V value)方法,是新增节点?还是更新节点?

就看你找到的跳表中0层最右的那个<key的cur节点了,
有了cur,
(1)咱们获取cur的下一个节点,如果等于key,岂不就是需要更新?
(2)如果不等key,那就是在这插入新节点。
在这里插入图片描述
key给了不能为null,value给了
(1)咱们获取cur的下一个节点,如果等于key,岂不就是需要更新?
next不null,且next.isEqual(key)为true,就是相等
直接让next.value=value;

(2)如果不等key,那就是在这插入新节点。
要么next=null,要么next.isLess(key)为true,就是不等于
必然跳表新增节点,size++
1)怎么增?根据概率p决定,新节点的层数++,节点从0–nL都要让nextNodes加null
2)如果新节点的层数nL过高,要让root的maxLevel=nL,咱们上面说过了,然后默认加入null,扩充层呗

3)level从maxLevel开始,pre=head开始,每一层,让pre抓取本层中最右的<key的节点
然后让新节点,挂在pre之后,新节点挂原来pre的下一个节点,就是插入新节点的意思
level–,每一层都要去挂,这就是新增节点的那段操作,上面咱们说透了。

挂的时候,新节点没没有level这层是没法加的哦!!!
在这里插入图片描述
Math.random()可以产生0–1之间的随机数

手撕更新或者新增节点的代码:

        //put方法,可能是更细腻value,可能是新增节点
        public void put(K key, V value){
            //key给了不能为null,value给了
            if (key == null) return;
            //就看你找到的跳表中0层最右的那个<key的cur节点了,
            NodeSL<K, V> cur = mostRightLessKeyInSLMap(key);
            //有了cur,
            //(1)咱们获取cur下一个节点,如果等于key,岂不就是需要更新?
            NodeSL<K, V> next = cur.nextNodes.get(0);//0层必然存在的
            //next不null,且next.isEqual(key)为true,就是相等
            //直接让next.value=value;
            if (next != null && next.isKeyEqual(key)) next.value = value;
            //(2)如果不等key,那就是在这插入新节点。
            else {
                //要么next=null,要么next.isLess(key)为true,就是不等于
                //必然跳表新增节点,size++
                size++;
                //1)怎么增?根据概率p决定,新节点的层数++,节点从0--nL都要让nextNodes加null
                int nL = 0;//0层必然有
                while (Math.random() < PROBABLITY) nL++;//直到大于0.5停止
                int level = nL;
                NodeSL<K, V> newNode =  new NodeSL<>(key, value);//新增好
                while (level >= 0){
                    newNode.nextNodes.add(null);
                    level--;
                }
                //2)如果新节点的层数nL过高,要让root的maxLevel=nL,咱们上面说过了,然后默认加入null,扩充层呗
                while (maxLevel < nL){
                    head.nextNodes.add(null);//新加几个层
                    maxLevel++;
                }
                //3)level从maxLevel开始,pre=head开始,每一层,让pre抓取本层中最右的<key的节点
                level = maxLevel;
                NodeSL<K, V> pre = head;//准备挂接新节点到pre后面
                while (level >= 0){
                    pre = findInLevelMostRightLessKey(pre, key, level);
                    //然后让新节点,挂在pre之后,新节点挂原来pre的下一个节点,就是插入新节点的意思
                    if (level <= nL){//新节点有level这层吗?不一定哦
                        newNode.nextNodes.set(level, pre.nextNodes.get(level));//后插
                        pre.nextNodes.set(level, newNode);//前挂
                    }
                    //level--,每一层都要去挂,这就是新增节点的那段操作,上面咱们说透了。
                    level--;//直到0层全挂好
                }
            }
        }

head层数maxLevel不够,需要跟着加,比如下图,4节点新来的,它有3层,但是head现在只有2层,就需要让head新增一个层
在这里插入图片描述
从最高层呢刚开始挂,新节点,每一层,找到最右不能再往右的节点pre,挂插
在这里插入图片描述

一定要注意哈!挂的时候,新节点没没有level这层是没法加的哦!!!
比如root有7层,新来的节点只有3层,那不好意思,7654这些层,都不需要挂,而且,我们能快速绕过很多点,来到3层的最右的小于key的节点,挂上新节点,速度快。
在这里插入图片描述


containsKey(K key)方法

直接找跳表0层中最右那个<key的节点cur,然后判断next=cur.next是否与key相等,相等就包含,否则不包含

        //包含key吗,跳表?
        public boolean containsKey(K key){
            //直接找跳表0层中最右那个<key的节点cur,
            NodeSL<K, V> cur = mostRightLessKeyInSLMap(key);
            // 然后判断next=cur.next是否与key相等,相等就包含,否则不包含
            NodeSL<K, V> next = cur.nextNodes.get(0);
            return next != null && next.isKeyEqual(key);
        }

get(key)方法

完全跟containsKey方法一样
直接找跳表0层中最右那个<key的节点cur,然后判断next=cur.next是否与key相等,相等就包含,然后get第0层的这个value即可
否则就是返回null

        //get(key)方法
        //完全跟containsKey方法一样
        //直接找跳表0层中最右那个<key的节点cur,然后判断next=cur.next是否与key相等,
        // 相等就包含,然后get第0层的这个value即可
        public V get(K key){
            //直接找跳表0层中最右那个<key的节点cur,
            NodeSL<K, V> cur = mostRightLessKeyInSLMap(key);
            // 然后判断next=cur.next是否与key相等,相等就包含,否则不包含
            NodeSL<K, V> next = cur.nextNodes.get(0);
            return next != null && next.isKeyEqual(key) ? next.value : null;
        }

remove(K key)方法

(1)先查跳表中有key吗?有才删除
(2)从最高层开始,level=maxLevel,每一个level,先拿level层最右那个<key的节点cur
next=cur.next,next真的等于key吗?是的
将cur跳指next的next节点,完成单链表的删除
set方法就是指针连接,设置下一个节点
(3)注意!!!
当level!=0的时候,要检查,看看删除key这个节点时,是否head需要缩层
第0层永远不能缩
当cur确实为head时,且cur.nextNodes(level)=null,当前level层只有root节点了,就可以让上面这个没有连接节点的null删除。

下图,最高那个节点删除之后,发现head的最高层maxLevel就连接null了,没用删除
这个maxLevel就是因为当初新节点来的时候扩充的,现在这个节点废了,自然也要删除这个扩充的层,懂吧?
在这里插入图片描述
手撕删除的代码

        //删除key
        public void remove(K key){
            if (containsKey(key)){
                //(1)先查跳表中有key吗?有才删除
                //(2)从最高层开始,level=maxLevel,
                int level = maxLevel;
                NodeSL<K, V> cur = head;
                while (level >= 0){
                    //每一个level,先拿level层最右那个<key的节点cur
                    findInLevelMostRightLessKey(head, key, level);
                    NodeSL<K, V> next = cur.nextNodes.get(level);
                    //next=cur.next,next真的等于key吗?是的
                    if (next != null && next.isKeyEqual(key)){
                        //将cur跳指next的next节点,完成单链表的删除
                        //set方法就是指针连接,设置下一个节点
                        cur.nextNodes.set(level, next.nextNodes.get(level));
                    }

                    //(3)注意!!!
                    //当level!=0的时候,要检查,看看删除key这个节点时,是否head需要缩层
                    //第0层永远不能缩
                    if (level != 0 && cur == head && cur.nextNodes.get(level) == null){
                        //当cur确实为head时,且cur.nextNodes(level)=null,
                        // 当前level层只有root节点了,就可以让上面这个没有连接节点的null删除。
                        head.nextNodes.remove(level);
                        maxLevel--;//root因为新节点扩充,现在因为删除缩小
                    }
                    
                    level--;//每层都操作
                }
            }
        }

你要注意:美团,微软,Airbnb都考过,面试官让你现场手撕跳表的部分函数的代码!!!
你要注意:美团,微软,Airbnb都考过,面试官让你现场手撕跳表的部分函数的代码!!!
你要注意:美团,微软,Airbnb都考过,面试官让你现场手撕跳表的部分函数的代码!!!

所以你要搞清楚了这些跳表的知识!


firstKey()方法

获取跳表的第0个节点,最左的节点key
很简单,就是第0层的第一个,超级简单

        //# firstKey(K key)方法
        //很简单,就是第0层的第一个,超级简单
        public K firstKey(){
            return head.nextNodes.get(0) != null ? head.nextNodes.get(0).key : null;
        }

lastKey()方法

获取跳表的第0层的最后个节点,key
但显然不是从第0层第一个开始索引
有跳表,那就跳着找,从level=maxL开始索引,上面越过一个点,下面越过一大堆节点
跟查找整个跳表最右<key的几点cur类似,只不过这是0层最右那个
我们要让每一层往右,最右那个cur遇见null才往下走,嵌套俩while循环
外循环控制level–
内循环控制一层使劲往右查找
在这里插入图片描述

        //lastKey()方法
        //获取跳表的第0层的最后个节点,key
        public K lastKey(){
            //有跳表,那就跳着找,从level=maxL开始索引,上面越过一个点,下面越过一大堆节点
            NodeSL<K, V> cur = head;
            int level = maxLevel;
            while (level >= 0){//高层一直找到0层,右下,右下,单链表使劲往右查
                //跟查找整个跳表最右<key的几点cur类似,只不过这是0层最右那个
                NodeSL<K, V> next = cur.nextNodes.get(level);//当前level层cur的下一个节点,方便右移
                while (next != null){//只要cur不为null,继续右移
                    cur = next;
                    next = cur.nextNodes.get(level);//往右移动
                }
                //第0层最右那个cur就是结果
                level--;
            }
            return cur.key;
        }

ceilingKey()方法

找>=key的那个节点的key
找到跳表中0层最右的<key的节点cur
cur.next要么=key,要么>key,就是next了

        //# ceilingKey()方法
        //找>=key的那个节点的key
        public K ceilingKey(K key){
            //找到跳表中0层最右的<key的节点cur
            NodeSL<K, V> cur = mostRightLessKeyInSLMap(key);
            NodeSL<K, V> next = cur.nextNodes.get(0);//0层哦,包含N个点,所有
            //cur.next要么=key,要么>key,就是next了
            return next != null ? next.key : null;
        }

floorKey()方法

找<=key的那个节点的key
找到跳表中0层最右的<key的节点cur
cur.next要么=key,那就返回next.key
要么>key,那就要返回cur.key,咱们要的是<=key哦!!!

        //# floorKey()方法
        //找<=key的那个节点的key
        public K floorKey(K key){
            //找到跳表中0层最右的<key的节点cur
            NodeSL<K, V> cur = mostRightLessKeyInSLMap(key);
            NodeSL<K, V> next = cur.nextNodes.get(0);//0层哦,包含N个点,所有
            //cur.next要么=key,那就返回next.key
            //要么>key,那就要返回cur.key,咱们要的是<=key哦!!!
            return next != null && next.isKeyEqual(key) ? next.key : cur.key;
            //next为null或者不等,则cur就是<=k的
        }

总结跳表类SkipListMap的所有方法

跳表虽然与搜索二叉树的结构完全不同,但是它能实现所有有序表的操作,复杂度仍然是o(log(n))
这就是具有先进思想的跳表!!!

方法们都有:
(1)每一个层level,都要向右不断找,不断往右查,往右到不能再往右了,停,此刻cur<key;
findInLevelMostRightLessKey(NodeSL<K, V> cur, K key, int level)函数
(2)/从root的最高层maxLevel处,开始查找并返回跳表的0层最右的<key的节点cur
mostRightLessKeyInSLMap(K key)函数
(3)put方法,可能是更细腻value,可能是新增节点“”put(K key, V value)函数
(4)包含key吗,跳表?:containsKey(K key)函数
(5)相等就包含,然后get第0层的这个value即可:get(K key)函数
(6)删除key:remove(K key)
(7)firstKey(K key)方法
(8)lastKey()方法
(9)ceilingKey()方法
(10)floorKey()方法

    //跳表类
    public static class SkipListMap<K extends Comparable<K>, V>{
        //成员变量
        private static final double PROBABLITY = 0.5;
        public NodeSL<K, V> head;//头结点,最左边那个
        public int size;//整体节点个数
        public int maxLevel;//跳表中root的最高层数

        //构造函数
        public SkipListMap(){
            size = 0;
            head = new NodeSL<>(null, null);//这个key是全局最小
            head.nextNodes.add(null);至少1层是有的,最底层先加null
            maxLevel = 0;//至少1层是有的,最底层
        }

        //成员方法
        //每一个层level,都要向右不断找,不断往右查,往右到不能再往右了,停,此刻cur<key;
        public NodeSL<K, V> findInLevelMostRightLessKey(NodeSL<K, V> cur, K key, int level){
            NodeSL<K, V> next = cur.nextNodes.get(level);//当前level层cur的下一个节点,方便右移
            while (next != null && cur.isKeyLess(next.key)){
                //只要cur比next小,继续右移,否则返回cur
                cur = next;
                next = cur.nextNodes.get(level);//往右移动
            }
            //一旦cur>=next
            return cur;
        }

        //从root的最高层maxLevel处,开始查找并返回跳表的0层最右的<key的节点cur
        public NodeSL<K, V> mostRightLessKeyInSLMap(K key){
            if (key == null) return null;//null没法比
            //(1)从cur=root开始,
            NodeSL<K, V> cur = head;
            int level = maxLevel;//从root的最高层往右扫
            while (level >= 0){
                // (1)向右同一层中查找,只要cur<key继续往右查,往右到不能再往右了,停,此刻cur<key;
                cur = findInLevelMostRightLessKey(cur, key, level--);
                //(2)在cur内部层数level--,继续循环(1),直到level=0的最右那个cur<key,返回cur。
            }

            return cur;
        }

        //put方法,可能是更细腻value,可能是新增节点
        public void put(K key, V value){
            //key给了不能为null,value给了
            if (key == null) return;
            //就看你找到的跳表中0层最右的那个<key的cur节点了,
            NodeSL<K, V> cur = mostRightLessKeyInSLMap(key);
            //有了cur,
            //(1)咱们获取cur下一个节点,如果等于key,岂不就是需要更新?
            NodeSL<K, V> next = cur.nextNodes.get(0);//0层必然存在的
            //next不null,且next.isEqual(key)为true,就是相等
            //直接让next.value=value;
            if (next != null && next.isKeyEqual(key)) next.value = value;
            //(2)如果不等key,那就是在这插入新节点。
            else {
                //要么next=null,要么next.isLess(key)为true,就是不等于
                //必然跳表新增节点,size++
                size++;
                //1)怎么增?根据概率p决定,新节点的层数++,节点从0--nL都要让nextNodes加null
                int nL = 0;//0层必然有
                while (Math.random() < PROBABLITY) nL++;//直到大于0.5停止
                int level = nL;
                NodeSL<K, V> newNode =  new NodeSL<>(key, value);//新增好
                while (level >= 0){
                    newNode.nextNodes.add(null);
                    level--;
                }
                //2)如果新节点的层数nL过高,要让root的maxLevel=nL,咱们上面说过了,然后默认加入null,扩充层呗
                while (maxLevel < nL){
                    head.nextNodes.add(null);//新加几个层
                    maxLevel++;
                }
                //3)level从maxLevel开始,pre=head开始,每一层,让pre抓取本层中最右的<key的节点
                level = maxLevel;
                NodeSL<K, V> pre = head;//准备挂接新节点到pre后面
                while (level >= 0){
                    pre = findInLevelMostRightLessKey(pre, key, level);
                    //然后让新节点,挂在pre之后,新节点挂原来pre的下一个节点,就是插入新节点的意思
                    if (level <= nL){//新节点有level这层吗?不一定哦
                        newNode.nextNodes.set(level, pre.nextNodes.get(level));//后插
                        pre.nextNodes.set(level, newNode);//前挂
                    }
                    //level--,每一层都要去挂,这就是新增节点的那段操作,上面咱们说透了。
                    level--;//直到0层全挂好
                }
            }
        }

        //包含key吗,跳表?
        public boolean containsKey(K key){
            //直接找跳表0层中最右那个<key的节点cur,
            NodeSL<K, V> cur = mostRightLessKeyInSLMap(key);
            // 然后判断next=cur.next是否与key相等,相等就包含,否则不包含
            NodeSL<K, V> next = cur.nextNodes.get(0);
            return next != null && next.isKeyEqual(key);
        }

        //get(key)方法
        //完全跟containsKey方法一样
        //直接找跳表0层中最右那个<key的节点cur,然后判断next=cur.next是否与key相等,
        // 相等就包含,然后get第0层的这个value即可
        public V get(K key){
            //直接找跳表0层中最右那个<key的节点cur,
            NodeSL<K, V> cur = mostRightLessKeyInSLMap(key);
            // 然后判断next=cur.next是否与key相等,相等就包含,否则不包含
            NodeSL<K, V> next = cur.nextNodes.get(0);
            return next != null && next.isKeyEqual(key) ? next.value : null;
        }

        //删除key
        public void remove(K key){
            if (containsKey(key)){
                //(1)先查跳表中有key吗?有才删除
                //(2)从最高层开始,level=maxLevel,
                int level = maxLevel;
                NodeSL<K, V> cur = head;
                while (level >= 0){
                    //每一个level,先拿level层最右那个<key的节点cur
                    findInLevelMostRightLessKey(head, key, level);
                    NodeSL<K, V> next = cur.nextNodes.get(level);
                    //next=cur.next,next真的等于key吗?是的
                    if (next != null && next.isKeyLess(key)){
                        //将cur跳指next的next节点,完成单链表的删除
                        //set方法就是指针连接,设置下一个节点
                        cur.nextNodes.set(level, next.nextNodes.get(level));
                    }

                    //(3)注意!!!
                    //当level!=0的时候,要检查,看看删除key这个节点时,是否head需要缩层
                    //第0层永远不能缩
                    if (level != 0 && cur == head && cur.nextNodes.get(level) == null){
                        //当cur确实为head时,且cur.nextNodes(level)=null,
                        // 当前level层只有root节点了,就可以让上面这个没有连接节点的null删除。
                        head.nextNodes.remove(level);
                        maxLevel--;//root因为新节点扩充,现在因为删除缩小
                    }

                    level--;//每层都操作
                }
            }
        }

        //# firstKey(K key)方法
        //很简单,就是第0层的第一个,超级简单
        public K firstKey(){
            return head.nextNodes.get(0) != null ? head.nextNodes.get(0).key : null;
        }

        //lastKey()方法
        //获取跳表的第0层的最后个节点,key
        public K lastKey(){
            //有跳表,那就跳着找,从level=maxL开始索引,上面越过一个点,下面越过一大堆节点
            NodeSL<K, V> cur = head;
            int level = maxLevel;
            while (level >= 0){//高层一直找到0层,右下,右下,单链表使劲往右查
                //跟查找整个跳表最右<key的几点cur类似,只不过这是0层最右那个
                NodeSL<K, V> next = cur.nextNodes.get(level);//当前level层cur的下一个节点,方便右移
                while (next != null){//只要cur不为null,继续右移
                    cur = next;
                    next = cur.nextNodes.get(level);//往右移动
                }
                //第0层最右那个cur就是结果
                level--;
            }
            return cur.key;
        }

        //# ceilingKey()方法
        //找>=key的那个节点的key
        public K ceilingKey(K key){
            //找到跳表中0层最右的<key的节点cur
            NodeSL<K, V> cur = mostRightLessKeyInSLMap(key);
            NodeSL<K, V> next = cur.nextNodes.get(0);//0层哦,包含N个点,所有
            //cur.next要么=key,要么>key,就是next了
            return next != null ? next.key : null;
        }

        //# floorKey()方法
        //找<=key的那个节点的key
        public K floorKey(K key){
            //找到跳表中0层最右的<key的节点cur
            NodeSL<K, V> cur = mostRightLessKeyInSLMap(key);
            NodeSL<K, V> next = cur.nextNodes.get(0);//0层哦,包含N个点,所有
            //cur.next要么=key,那就返回next.key
            //要么>key,那就要返回cur.key,咱们要的是<=key哦!!!
            return next != null && next.isKeyEqual(key) ? next.key : cur.key;
            //next为null或者不等,则cur就是<=k的
        }


    }//跳表结束

总结

提示:重要经验:

1)跳表的思想先进,多个大厂要求你现场手撕成员方法的代码,速度快,结构简单,就是操作俩表的代码比价繁杂,思想搞透彻了,就好写代码。
2)跳表可以实现有序表的所有功能,而且用于redis缓存啥的利于序列化
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值