什么是跳表?


一、什么是跳表?

跳表其实就是多层链表,用来解决单层链表复杂度较高的问题。

跳表的特点:

  • 多层链表组成
  • 每一层链表都是有序的
  • 最后一层链表包含所有数据
  • 第二层链表开始包含前面层所有数据

二、跳表操作

1、插入

跳表插入逻辑跟链表一样的是要找到插入节点的前一个节点即可,但不同的是跳表还要指向下层的节点,相关代码为:

public class SkipListNode {
    private int val;
    private SkipListNode next;
    private SkipListNode down;
}

另外跳表还会通过随机函数来获得一个随机值,来判断当前插入元素要从哪一层开始往下插入,相关代码为:

public class SkipList {
    /** 省略其他代码 **/
    private int randLevel() {
        // 节点越少的层级,随机概率越低
        // 1的概率是0.5,2的概率是0.25,3的概率是0.125...
        int level = 1;
        
        while (Math.random() < 0.5f && level < MAX_LEVEL) {
            level ++;
        }
        
        return level;
    }
}

插入步骤如下:

1.判断level层往上的层级有没有创建,没有的话则先创建,如图所示

2.找到待插入节点的前一个节点,连接next指针

3.类似步骤2,连接down指针

对应代码为:

public class SkipList {
    /** 省略其他代码 **/
    public void add(int num) {
        int level = randLevel();// 从第几层开始插入,随机数。
        // 记录待插入节点的前一个节点。
        SkipListNode[] preNodes = new SkipListNode[level];

        // 第一步:如果跳表层数比较少,在上面添加,层数至少为 level 。
        if (curLevelCount < level) {
            SkipListNode beforeHead = head;
            head = new SkipListNode(-1, null);// 更新 head 节点。
            SkipListNode curHead = head;
            // 在上面添加每层的头节点。
            for (int i = curLevelCount; i < level - 1; i++) {
                SkipListNode node = new SkipListNode(-1, null);
                curHead.down = node;
                curHead = node;
            }
            // 最后创建的链表头节点和之前的头节点连在一起。
            curHead.down = beforeHead;
        }

        // 第二步:从上往下查找每层待插入节点的前一个节点。
        SkipListNode pre = head;
        // 上层不需要插入的跳过。
        for (int i = curLevelCount - 1; i >= level; i--)
            pre = pre.down;
        // 从当前层往下每层都要插入该节点,找出每层待插入节点的前一个节点。
        for (int i = level - 1; i >= 0; i--) {
            while (pre.next != null && pre.next.val < num)
                pre = pre.next;
            preNodes[i] = pre;// 记录前一个节点。
            pre = pre.down;
        }

        // 第三步:节点插入,插入的时候不光有 next 指针,而且还有 down 指针。
        SkipListNode topNode = null;
        // 把新建节点 node 插到该层下面的每一层。
        for (int i = level - 1; i >= 0; i--) {
            // 创建新节点。
            SkipListNode node = new SkipListNode(num, preNodes[i].next);
            // 链表的插入,参见单向链表的插入。
            preNodes[i].next = node;
            // 上下也要连接。
            if (topNode != null)
                topNode.down = node;
            topNode = node;
        }
        if (level > curLevelCount)// 更新跳表的层级,用来记录当前跳表的层级。
            curLevelCount = level;
    }
}

2、查询

跳表查询从最上面一层开始,每层往后查找,如果后面没有了节点或者节点值比目标值大,则往下层查找,,如图所示:

对应代码为:

public class SkipList {
    /** 省略其他代码 **/
    // 查找值为 target 的节点。
    public boolean search(int target) {
        SkipListNode pre = head;
        while (pre != null) {
            // 如果当前节点值小于 target ,需要到右边查找,如果右边没有节点就到下边查找。
            if (pre.val < target) {
                if (pre.next == null)// 右边没有节点,到下边查找
                    pre = pre.down;
                else
                    pre = pre.next.val > target ? pre.down : pre.next;
            } else if (pre.val == target) {// 如果找到直接返回。
                return true;
            } else {
                return false;// 如果当前节点值大于 target ,说明没有,直接返回 false 。
            }
        }
        return false;
    }
}

3、删除

跳表删除跟跳表添加类似,从上到下每一层都找到待删除节点的前一个节点,删除后如果该层没有其他节点了,则需要删除该层

对应代码为:

public class SkipList {
    /** 省略其他代码 **/
    public boolean remove(int num) {
        // 删除链表和插入链表类似,都是需要先找到插入或删除链表的前一个节点。
        int topIndex = -1;// 从当前层开始往下每层都要删除。
        // 查找待删除节点的前一个节点,从上面一层开始查找。
        SkipListNode pre = head;
        for (int i = curLevelCount - 1; i >= 0; i--) {
            while (pre.next != null && pre.next.val < num)
                pre = pre.next;
            // 如果找到就终止查找,表示在当前层以及他下面的所有层都要删除该节点。
            if (pre.next != null && pre.next.val == num && topIndex == -1) {
                topIndex = i;
                break;
            }
            if (pre.down == null)// 如果跳表中没有要删除的节点,返回 false 。
                return false;
            pre = pre.down;// 当前层没找到就往下一层继续查找。
        }

        if (topIndex == -1)// 如果跳表中没找到要删除的节点,返回 false 。
            return false;

        // 从 topIndex 层开始,他下面的每一层都要删除。
        for (int i = topIndex; i >= 0; i--) {
            if (pre.next != null)// 节点删除,参考单向链表删除。
                pre.next = pre.next.next;
            pre = pre.down;// 继续下一层的删除。
            if (pre != null)// 找到待删除节点的前一个节点。
                while (pre.next != null && pre.next.val != num)
                    pre = pre.next;
        }
        // 如果上面一层的节点被删除完了,要更新 curLevelCount 的值 ,还要更新 head节点。
        SkipListNode cur = head;
        while (curLevelCount > 1 && cur.next == null) {
            cur = cur.down;
            head = cur;
            curLevelCount--;
        }
        return true;
    }
}

文章作者: GaryLee
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 GaryLee !
  目录