跳表学习笔记

跳表概述

为啥会有跳表

跳表也是为了快速查找而提出的一种数据结构。细想我在此前学到的关于查找的知识。

  • 有序数组用二分法,复杂度o(lgn)
  • 对于树性结构,有BST, 平衡树, 平衡树又演化出很多结构,如B树, B+树,红黑树,AVL树。最好的时间复杂度是O(lgn)。

但是对于链表结构,却没有一种帮助我们快速查找的数据,链表时间复杂度是O(n), 跳表就产生了。它本质上是一种多级链表,通过增加数据的冗余来换取查找的时间复杂度,属于空间换时间的思想。不过呢,其实空间也不会消耗太多,因为冗余的只是节点指针。

优点分析

  • 相比红黑树来说,跳表实现简单,你面试的时候是可以手写出来的,而且插入和删除的操作也不难。红黑树里面大量的自旋操作常常让人迷惑。
  • 数据是自排序的,这点和MYSQL里面的B+树很像,默认是从小到大排序的。利用这一点就是快速进行范围查找,而不用真正地排序。

技术细节

结构

  • 第一个粉色的位置是头节点,引入这个的作用是为了屏蔽一些插入和删除时的差异化。
  • 最左侧表示节点的值, 可以看出,节点的值是从小到大排序的。

查找过程

例子:

我们以查找3的为例:

  • 从头节点的最高层开始,也就是从节点1(图中用圆圈表示)开始,寻找第最后一个小于3的节点,找到了节点1
  • 节点1下移动一层,从节点2开始,寻找最后一个小于3的节点, 找到了节点3。
  • 节点3下移动一层,从节点4开始, 寻找第一个小于3的节点开始,找到了节点4。
  • 此时层数是0了,看节点4的下一个节点的值是否是3, 发现是的,就找到了,反之就找不到。

算法的优化: 可以在查找过程中,判断下一个节点是不是3, 如果是直接返回了,不必再深入了。

插入过程

我们以插入2为例子

插入2首先得生成一个随机高度,这个高度不得高于头节点的高度。这里可以用Java的random来实现。

  • 首先进行一次查找,将小于2的节点都记录下来。 查找结束后,我们将节点1, 2, 3, 4 记录下来。
  • 生成新的节点,同时设定新的节点的高度,这个可以通过随机生成,但是高度不得大于头节点的高度。
  • 修改前后指针,主要改两个地方,新节点的下一个节点指针,新节点前面的节点指针。
    • 节点4 的下一个节点改为节点4的下一个节点,节点4的下一个节点改为新节点。
    • 节点6的下一个节点改为节点3的下一个节点,节点3的下一个节点改为新节点。
    • 节点7 的下一个节点改为节点2的下一个节点,节点2的下一个节点改为新节点。

删除操作

删除操作和插入类似,也是现有一个查找的过程,只不过首先要看看在没在,如果不存在要删除的元素,则删除是失败的。在的话,更改节点指针。还是以下图为例子,我们要删除2。

  • 首先查找每一层比2小的节点,并记录下来。此时,我们将节点1, 2,3,4 记录下来
  • 要删除的节点为值为2的这个节点,此时,我们修改这个节点的前置节点指针即可。
    • 修改节点4的下一个节点为节点4的下一个节点的下一个节点,也就是节点6。
    • 修改节点3的下一个节点为节点4的下一个节点的下一个节点。也就是节点7。
    • 修改节点2的下一个节点为节点4的下一个节点的下一个节点。也就是节点8。

更新操作

更新一个值,如果我们直接更新以后,会导致跳表的调整,这个操作也比较复杂。比较好的一种思路,就是先删除再插入。这样就避免的调整操作,这个思路可以在编码时学习。

代码

最末尾

应用场景

redis 里面的zset就是采用了跳表的思想,只不过它不是真正的条表,而是跳表和hash的结合。

zset的要满足的功能主要有两个

  • 要根据value 快速索引到score
  • 可以根据score进行查找或者排序

这里,根据value快速索引到score是靠hash实现的,而根据score进行排序或者查找是通过跳表实现了。这个跳表底层是双向链表,其它层是单向的。这个层数默认是64层官方是做了优化的,可以保证跳表比较扁平化,使得上层的节点少一点,提高查询速度。

缺点与改进

  • 这个缺点就是给每个节点分配层数的时候,可能都分配一些高度,导致链表出现每一层节点个数一样,这样时间复杂度就是普通链表差不多了。
/**
 * @Author Fizz Pu
 * @Date 2021/7/28 下午3:07
 * @Version 1.0
 * 失之毫厘,缪之千里!
 */

import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

public class SkipListDemo {

    // 最大高度
    int MAX_LEVEL= 8;

    public SkipListDemo(int maxLevel) {
        this.MAX_LEVEL = maxLevel;
    }

    public SkipListDemo() {

    }

    private ThreadLocalRandom random = ThreadLocalRandom.current();

    public static void main(String[] args) {
        SkipListDemo skipList = new SkipListDemo();
        // System.out.println(skipList.random.nextInt(0, 1));
        skipList.insert(1);
        skipList.insert(2);
        skipList.insert(3);
        System.out.println(skipList.find(1));
        System.out.println(skipList.find(2));
        System.out.println(skipList.find(3));
        System.out.println(skipList.find(0));
        System.out.println(skipList.delete(1));
        System.out.println(skipList.delete(0));
        System.out.println(skipList.delete(1));
        System.out.println(skipList.find(1));
    }


    private Node head = Node.getHeadNode(MAX_LEVEL);

    boolean find(int val) {
        Node curNode = head;
        Node nextNode;
        int level = curNode.getLevel();
        boolean isSkip; // 表示每一层链表是否有移动过

        // 从头节点的最高层开始查找
        while (level  >= 1) {
            isSkip = false;

            // 寻找最后一个小于val的节点, 等价成链表操作
            // 下一个节点存在并且小于val,才移动
            while ((nextNode = curNode.next.get(level - 1)) != null && nextNode.val < val) {
                curNode = nextNode;
                isSkip = true;
            }

            nextNode = curNode.next.get(level-1);
            if (nextNode != null && nextNode.val == val) {
                return true;
            }

            // 更新level, 并下移
            // 链表没有移动, 不用更新level, 直接减1
            if (isSkip) {
                level = curNode.getLevel();
            }
            level--;
        }

        nextNode = curNode.next.get(0);

        if (nextNode == null) {
            return false;
        }

        return nextNode.val == val;
    }

    int insert(int val) {
        // 查找
        int level = head.getLevel();
        Node curNode = head;
        Node nextNode;
        boolean isSkip;
        List<Node> nodes = new ArrayList<>();

        while (level - 1 >= 0) {
            isSkip = false;
            while ((nextNode = curNode.next.get(level-1)) != null && nextNode.val < val) {
                isSkip = true;
                curNode = nextNode;
            }
            nodes.add(curNode);
            if (isSkip) {
                level = curNode.getLevel();
            }
            level--;
        }

        // 生成新的节点
        int newNodeLevel = getLevel();
        Node newNode = new Node(val);

        // 修改后面和前面的节点
        int counts = Math.min(newNodeLevel, nodes.size());
        int nodesLen = nodes.size();
        int end = nodesLen - counts;
        int curLevel = 0;
        Node tmp;
        for (int i = nodesLen - 1; i >= end; i--) {
            tmp = nodes.get(i);
            newNode.next.add(tmp.next.get(curLevel));
            tmp.next.set(curLevel, newNode);
            curLevel++;
        }

        return 1;
    }

    int delete(int val) {
        // 查找
        int level = head.getLevel();
        Node curNode = head;
        Node nextNode;
        boolean isSkip;
        List<Node> nodes = new ArrayList<>();

        while (level - 1 >= 0) {
            isSkip = false;
            while ((nextNode = curNode.next.get(level-1)) != null && nextNode.val < val) {
                isSkip = true;
                curNode = nextNode;
            }
            nodes.add(curNode);
            if (isSkip) {
                level = curNode.getLevel();
            }
            level--;
        }

        nextNode = curNode.next.get(0);

        // 节点不存在
        if (nextNode == null || nextNode.val != val) {
            return 0;
        }

        // 开始删除
        // 修改指针即可
        int nextNodeLevel = nextNode.getLevel();
        int counts = Math.min(nextNodeLevel, nodes.size());
        int nodesLen = nodes.size();
        int end = nodesLen - counts;
        int curLevel = 0;
        Node tmp;
        for (int i = nodesLen - 1; i >= end; i--) {
            tmp = nodes.get(i);
            tmp.next.set(curLevel, nextNode.next.get(curLevel));
            curLevel++;

        }

        nextNode = null; // help GC
        return 1;
    }

    // 随机获得层数,但是不得大于MAX_LEVEL
    protected int getLevel() {
        if (MAX_LEVEL == 1) {
            return 1;
        }
        return random.nextInt(1, MAX_LEVEL);
    }

}

// 1 -> 2 -> 3 -> 4
class Node {
    int val;
    List<Node> next;

    public static Node getHeadNode(int maxLevel) {
        Node head = new Node(Integer.MIN_VALUE);
        {
            for (int i = 0; i < maxLevel ; i++) {
                head.next.add(null);
            }
        }
        return head;
    }

    public int getLevel() {
        return next.size();
    }

    public Node(int val) {
        this.val = val;
        this.next = new ArrayList<>();
    }
}


posted @ 2021-07-28 22:26  FizzPu  阅读(67)  评论(0编辑  收藏  举报