面试题之跳表

目录

  1. 概述
  2. 代码实现
  3. 常见面试题
  4. 参考文章

概述

本文主要讲解跳表的原理、代码实现以及常见的面试题。
跳表本质上是一种查找结构,相比于平衡树,不仅实现简单,而且插入、删除、查找的时间复杂度均为O(logN)。跳表其实就是链表,只是对有序的链表增加上附加的前进链接(增加是以随机化的方式进行的),所以在列表中的查找可以快速的跳过部分列表从而快速检索。
由于跳表在Redis的使用,导致面试中经常会被提及,所以深入了解跳表的实现非常必要。

代码实现

第一种实现方法

这种实现方法构建的跳表整体如下图所示:

这种实现方式中的SkipNode定义如下所示。

  private class SkipNode<K, V> {
        private K key;
        private V value;
        private SkipNode<K, V>[] forward;

        public SkipNode( K key, V value, int levels ) {
            this.key = key;
            this.value = value;
            this.forward = (SkipNode<K, V>[]) new SkipNode[levels+1];
            for (int i = 0; i <= levels; i++)
                this.forward[i] = null;
        }
        public K key() { return this.key; }
        public V value() { return this.value; }
        ······
    }

SkipNode定义中需要留意就是forward数组,这个数组的大小是随机的,大小代表这个节点的高度,数组中每个元素代表了这一层的下一个SkipNode。
下面来看insert的代码实现。

    /**
     * 将新值插入到链表中
     * @param k
     * @param newValue
     */
    public void insert(K k, V newValue) {
        int newLevel = randomLevel();
        // 如果随机的层大于现在的最大层, 进行层调整
        if (newLevel > level)
            adjustHead(newLevel);
        this.level = newLevel;
        SkipNode<K, V>[] update = (SkipNode<K, V>[]) new SkipNode[level+1];
        SkipNode<K, V> x = this.head;
        // 找寻每一层的插入位置
        for (int i=level; i>=0; i--) {
            while((x.forward[i] != null) &&
                    ((k.compareTo(x.forward[i].key())) > 0))
                x = x.forward[i];
            update[i] = x;
        }
        // 创建新节点
        x = new SkipNode<K, V>(k, newValue, newLevel);
        // 类似于链表插入
        for (int i=0; i <= newLevel; i++) {
            x.forward[i] = update[i].forward[i];
            update[i].forward[i] = x;
        }
        this.size++;
    }

从上面的代码中可以看出insert主要有几个步骤:

首先随机产生层数,创建新节点
每层遍历,得到新节点在每层插入的前一个节点
逐层插入新节点(类似于链表插入)

下面来看find的代码实现。

    public V find(K searchKey) {
        SkipNode<K, V> x = this.head;
        // 类似于一个下楼梯的过程
        for (int i=level; i>=0; i--)
            while ((x.forward[i] != null) &&
                    (searchKey.compareTo(x.forward[i].key()) > 0))
                x = x.forward[i];
        x = x.forward[0];
        if ((x != null) && (searchKey == x.key()))
            return x.value();
        else return null;
    }

find的过程比较简单,类似于生活中下楼梯,具体过程见上图中的红线所示。

第二种实现方法

这种方式最终创建的跳表如下所示。

SkipListEntry的定义如下。

public class SkipListEntry<K extends Comparable, V> {

    public K key;
    public V value;
    public int pos;
    public SkipListEntry up, down, left, right;

    // 构造函数
    public SkipListEntry(K k, V v) {
        key = k;
        value = v;
        up = down = left = right = null;
    }
}

skipList的初始化操作。 定义了头和尾节点,并且把它们相连接。

  public SkipList(){
        SkipListEntry p1, p2;

        p1 = new SkipListEntry<K, V>(null, null);
        p2 = new SkipListEntry<K, V>(null, null);
        head = p1;
        tail = p2;
        p1.right = p2;
        p2.left = p1;
        n = 0;
        h = 0;
        r = new Random();
    }

查找操作如下所示。查找操作依然类似于下楼梯。

     public SkipListEntry<K, V> findEntry(K k) {
        SkipListEntry<K, V> p = head;

        while ( true ) {
            //首先向右走
            while ( p.right.key != null &&
                    p.right.key.compareTo(k) <= 0 ) {
                p = p.right;
            }
            // 向下走
            if ( p.down != null )
            {
                p = p.down;
            }
            else break;    
        }
        return(p);
    }

添加节点的实现如下。

  public Object put (K k, V v)
    {
        SkipListEntry p, q;
        int       i;
        // 待插入的前一个位置
        p = findEntry(k);
        if ( k.equals( p.getKey())) {
            Object old = p.getValue();
            p.value = v;
            return old;
        }

        q = new SkipListEntry(k, v);
        q.left = p;
        q.right = p.right;
        p.right.left = q;
        p.right = q;

        i = 0;                   // Current level = 0
        // 随机插入
        while ( r.nextDouble() < 0.5 )
        {
            // 当前插入的是第i层
            if ( i >= h ) {
                SkipListEntry p1, p2;
                h = h + 1;
                p1 = new SkipListEntry(null,null);
                p2 = new SkipListEntry(null,null);
                p1.right = p2;
                p1.down  = head;
                p2.left = p1;
                p2.down = tail;
                head.up = p1;
                tail.up = p2;
                head = p1;
                tail = p2;
            }

            while ( p.up == null ){
                p = p.left;
            }
            p = p.up;

            SkipListEntry e = new SkipListEntry(k, null);  

            e.left = p;
            e.right = p.right;
            e.down = q;

            p.right.left = e;
            p.right = e;
            q.up = e;

            q = e;    

            i = i + 1;    

        }
        n = n + 1;
        return null;
    }

和第一种实现方式不同的是,第二种实现方法的插入操作在每一层都需要重新创建节点进行插入,空间浪费。所以推荐第一种实现方法。

常见面试题

redis为什么使用跳表,为什么不用红黑树

相比于红黑树、平衡二叉树,跳表不仅查找、插入、删除时间复杂度都是O(logN),并且实现简单很多。

跳表数据结构的实现

可以参考第一种实现方法。

参考文章

http://www.mathcs.emory.edu/~cheung/Courses/323/Syllabus/Map/skip-list-impl.html
https://redisbook.readthedocs.io/en/latest/internal-datastruct/skiplist.html

喜欢文章的可以关注公众号“面经详解”,里面很多常见面试题讲解。
图片说明

全部评论

相关推荐

最近群里有很多同学找我看简历,问问题,主要就是集中在明年三月份的暑期,我暑期还能进大厂嘛?我接下来该怎么做?对于我来说,我对于双非找实习的一个暴论就是title永远大于业务,你在大厂随随便便做点慢SQL治理加个索引,可能就能影响几千人,在小厂你从零到一搭建的系统可能只有几十个人在使用,量级是不一样的。对双非来说,最难的就是约面,怎么才能被大厂约面试?首先这需要一点运气,另外你也需要好的实习带给你的背书。有很多双非的同学在一些外包小厂待了四五个月,这样的产出有什么用呢?工厂的可视化大屏业务很广泛?产出无疑是重要的,但是得当你的实习公司到了一定的档次之后,比如你想走后端,那么中厂后端和大厂测开的选择,你可以选择中厂后端(注意,这里的中厂也得是一些人都知道的,比如哈啰,得物,b站之类,不是说人数超过500就叫中厂),只有这个时候你再去好好关注你的产出,要不就无脑大厂就完了。很多双非同学的误区就在这里,找到一份实习之后,就认为自己达到了阶段性的任务,根本不再投递简历,也不再提升自己,玩了几个月之后,美其名曰沉淀产出,真正的好产出能有多少呢?而实际上双非同学的第一份实习大部分都是工厂外包和政府外包!根本无产出可写😡😡😡!到了最后才发现晚了,所以对双非同学来说,不要放过任何一个从小到中,从中到大的机会,你得先有好的平台与title之后再考虑你的产出!因为那样你才将将能过了HR初筛!我认识一个双非同学,从浪潮到海康,每一段都呆不久,因为他在不断的投递和提升自己,最后去了美团,这才是双非应该做的,而我相信大部分的双非同学,在找到浪潮的那一刻就再也不会看八股,写算法,也不会打开ssob了,这才是你跟别人的差距。
迷茫的大四🐶:我也这样认为,title永远第一,只有名气大,才有人愿意了解你的简历
双非本科求职如何逆袭
点赞 评论 收藏
分享
评论
点赞
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务