golang/java实现跳表的数据结构

1、跳表数据结构说明

最近在写一款中间件,类似于redis的中间件,我准备用golang语言来编写,目前已经实现了redis的大部分数据结构,比如string、hash、set、list,而开始实现zset的时候发现要使用跳表,在网上实现的跳表数据结构感觉都不太合适,所以就自己来实现了一个,我也是瞅着空闲的时间来实现了一把,我也不知道性能如何,还没来得及测试,欢迎大家在此基础上进行修正和讨论;
跳表(Skip List)是一种概率性的动态数据结构,通常用于实现有序集合(如集合、映射等)的查找、插入和删除操作。它能够在平均情况下提供对数时间复杂度(O(log n)),比起传统的链表和二叉搜索树,它在实现上更简单。

1.1、跳表的基本结构

跳表由多个层级的链表构成,每一层都是对下一层链表的“跳跃”,因此得名“跳表”。最底层是一个有序的链表,其上层链表则是从下层链表中按一定规则选出的元素组成。跳表的结构通常由以下几个层级构成:

第0层(底层):这是最基础的一层,包含了所有的元素,形成一个有序链表。
第1层及以上:这些层是通过概率性的方式从第0层元素中选择一部分元素构成的。在每一层,节点的出现概率通常是固定的(例如每个节点有50%的概率出现在上一层)。

1.2、跳表的操作

查找(Search):

从最上层的链表开始查找,按照节点的值进行比较,如果当前节点的值比目标值小,就跳到下一个节点,否则下降到下一层继续查找。
这种方式可以减少查找的时间复杂度,期望时间为 O(log n)。
插入(Insert):

首先进行查找操作,找到插入位置。
然后根据一定的概率决定是否在上层创建新节点,最终插入节点到每一层对应的链表中。插入操作也有期望时间 O(log n)。
删除(Delete):

类似于查找,找到待删除的节点后,将其从每一层的链表中移除,时间复杂度为 O(log n)。

1.3、跳表的优点

简单易实现:跳表比起平衡二叉树(如AVL树、红黑树等)实现起来更加简单,尤其在处理动态变化的数据时。
动态平衡:跳表是基于概率的动态数据结构,不需要像平衡树那样进行显式的重平衡操作。
较好的空间利用:相比于红黑树的每个节点存储颜色、父子指针,跳表的每个节点只需要存储向后和向下的指针。

1.4、跳表的缺点

空间开销:由于需要维护多层链表,跳表的空间复杂度是 O(n),其中 n 是元素的数量。对于每个元素,可能需要在不同层级上维护多个指针。
不适用于非常小的数据集:对于非常小的数据集,跳表可能并不如线性结构(如链表)高效。
跳表在很多场景中都能提供较好的性能,特别是在需要频繁插入和删除操作的有序集合中,常用于数据库、缓存系统、分布式系统等场景。

1.5、我的实现和设计

我这边设计是按照我跳表的思想进行设计的,首先有一个垂直指针指向下一层,每一层有一个水平的单向指针指向了下一个元素,每个节点是作为一个Entry来构成,Entry里面包含了两个元素,一个是element,一个score,我这边只想做redis的zset的数据结构,所以就没考虑其他数据类型,当然是可以抽出一个通用的跳表结构,我优点忙,就难得整了,设计大概如下:

在这里插入图片描述
从图中可以清楚的指导,节点SkipListNode里面的有一个right指针和down指针分别对应水平指针和垂直指针,用来和水平节点和上下节点建立关系的;每一层的head节点本身不存在实际的数据,只作为一个头结点存在,我是根据自身的一些常见设计的,可能毕竟简单,但是够用足以,各位看官如果觉得哪里不合理的或者有更好的方式请在评论区讨论;

2、java的实现

本来我最早是用golang实现的,但是golang写起来感觉还是没java那么舒服,所以就先用java实现一下。

2.1、SkipListNode.java

class SkipListNode {
   
    String element; //元素的名称
    double score; //元素的分数
    SkipListNode right;//水平指针
    SkipListNode down;//垂直指针

    public SkipListNode(String element, double score) {
   
        this.element = element;
        this.score = score;
        this.right = null;
        this.down = null;
    }
}

2.2、SkipList.java

class SkipList {
   
    private static final int MAX_LEVEL = 8; // 最大层数
    private int currentMaxLevel = 1;       // 当前最大层数
    private SkipListNode head;
    private int levels;
    private Map<String, SkipListNode> ref;
    private int size;

    public SkipList() {
   
        this.head = new SkipListNode(null, Double.NEGATIVE_INFINITY); // 头节点,起始无穷小
        this.levels = 1;
        this.ref = new HashMap<>();
        size = 0;
    }

    // 随机生成层数
    private int randomLevel() {
   
        int level = 1;
        int maxAllowedLevel = Math.min(currentMaxLevel + 1, MAX_LEVEL);
        while (level < maxAllowedLevel && Math.random() < 0.5) {
   
            level++;
        }
        return level;
    }

    // 插入元素
    public void insert(String element, double score) {
   
        // Step 1: 查找插入位置,并记录路径
        List<SkipListNode> path = new ArrayList<>();
        SkipListNode current = head;

        // 查找插入位置并记录路径
        while (current != null) {
   
            while (current.right != null && current.right.score < score) {
   
                current = current.right;
            }
            path.add(current); // 记录路径
            current = current.down;
        }

        // Step 2: 生成新节点的层数
        int newLevel = randomLevel();
        currentMaxLevel = Math.max(currentMaxLevel, newLevel); // 更新当前最大层数

        // Step 3: 创建新节点,并逐层插入
        SkipListNode downNode = null; // 用来连接下层的节点
        SkipListNode newNode = new SkipListNode(element, score); // 只创建一次新节点

        // Step 4: 按层插入节点
        for (int i = 0; i < newLevel; i++) {
   
            // 如果路径中没有足够的层,则扩展头节点
            if (i >= path.size()) {
   
                SkipListNode newHead = new SkipListNode(null, Double.NEGATIVE_INFINITY); // 创建新头节点
                newHead.down = head; // 将原头节点挂到新头节点下层
                head = newHead; // 更新头节点
                levels++;
                path.add(0, newHead); // 将新头节点添加到路径中
            }

            // 当前层的前一个节点
            SkipListNode prev = path.get(path.size() - 1 - i);

            // 插入当前层的新节点
            newNode.right = prev.right; // 将当前节点的 right 指向原来节点的 right
            prev.right = newNode;       // 将前一个节点的 right 指向新节点
            newNode.down = downNode;    // 将新节点的 down 指向下层节点
            downNode = newNode;         // 更新下层节点为当前新节点

            // 如果新层还需要节点,继续创建新节点实例
            if (i < newLevel - 1) {
   
                newNode = new SkipListNode(element, score); // 在此处复用对象
            }
        }
        ref.put(element, newNode);
        size++;
    }


    // 查找单个元素
    public SkipListNode find(String element) {
   
        SkipListNode current = head;
        while (current != null
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值