手撕跳表/数据结构

news/2025/2/25 22:45:50
  • 昨天leetcode每日一题是跳表,之前学redis就没去写跳表,这次就逃不过了。

  • 这里使用了len数组,来表示每个数字之间的间隔,方便复杂的查询功能。

  • 主要问题有

  1. 为什么len数组记录的是数字之间的间隔,不是每一层从头到尾的次序?
    如果不用间隔,增删的时候会很麻烦,会改变后面的数据
  2. MAXL为什么是20?
    这个取决于数据量,是2的几次方,是几次方就填多少。
  3. 为什么决定数据有几层要随机?
    这里用的是java的random函数,数字位于[0, 1),这个时候对半分,小于0.5就加,大于等于就不加,这样就类似于抛硬币,概率为二分之一,这个时候可以类似得到完全二叉树的结构,使得得到log(N)的时间复杂度。
  • 下面附上代码
java">class Skiplist {
    // 跳跃最大层数限制
    public static int MAXL = 20;

    public static int MAXN = 100001;

    // 空间使用计数
    public static int cnt;

    // 节点的key
    public static int[] key = new int[MAXN];

    // 节点key的计数
    public static int[] count = new int[MAXN];

    // 节点拥有多少层指针
    public static int[] level = new int[MAXN];

    // 节点每一层指针指向哪个节点
    public static int[][] next = new int[MAXN][MAXL + 1];

    // 节点每一层指针的长度(底层跨国多少数、左开右闭)
    public static int[][] len = new int[MAXN][MAXL + 1];

    public Skiplist() {
        cnt = 2; // 头节点是1,新节点从2开始
        key[1] = Integer.MIN_VALUE;
        level[1] = MAXL;
        // 初始化头节点的next和len数组
        for (int i = 1; i <= MAXL; i++) {
            next[1][i] = 0;
            len[1][i] = 0;
        }
    }
    
    // 从i号节点的h层,返回key为num的节点,空间编号为多少
    public static int find(int i, int h, int num) {
        // 非底层,逐层向下查找
        while (next[i][h] != 0 && key[next[i][h]] < num) {
            i = next[i][h];
        }
        
        if (h == 1) {
            // 在底层,检查下一个节点是否是目标值
            if (next[i][h] != 0 && key[next[i][h]] == num) {
                return next[i][h];
            }
            return 0;  // 没找到返回0
        }
        
        // 递归到下一层继续查找
        return find(i, h - 1, num);
    }

    // 扔骰子决定节点的层数
    public static int random() {
        int ans = 1;
        while (Math.random() < 0.5) {
            ans++;
        }
        return Math.min(ans, MAXL);
    }

    // 返回target是否存在于跳表中。
    public boolean search(int t) {
        return find(1, MAXL, t) != 0; // 判断是否找到
    }

    // 插入一个元素到跳表
    public void add(int t) {
        int j = find(1, MAXL, t);
        if (j != 0) {
            addCount(1, MAXL, t);
        } else {
            int newNode = cnt; // 当前cnt作为新节点编号
            key[newNode] = t;
            count[newNode] = 1;
            level[newNode] = random();
            addNode(1, MAXL, newNode);
            cnt++; // 递增cnt
        }
    }

    public boolean erase(int t) {
        int j = find(1, MAXL, t);
        if (j != 0) {
            if (count[j] > 1) {
                removeCount(1, MAXL, t);
            } else {
                removeNode(1, MAXL, j);
            }
            return true;
        }
        return false;
    }

    // 加词频
    public static void addCount(int i, int h, int num) {
        while (next[i][h] != 0 && key[next[i][h]] < num) {
            i = next[i][h];
        }
        if (h == 1) {
            count[next[i][h]]++;
        } else {
            addCount(i, h - 1, num);
        }
        len[i][h]++;
    }

    // 当前i号节点的h层,插入空间编号为j的节点
    public static int addNode(int i, int h, int j) {
        int rightCnt = 0;
        while (next[i][h] != 0 && key[next[i][h]] < key[j]) {
            rightCnt += len[i][h];
            i = next[i][h];
        }
        if (h == 1) {
            next[j][h] = next[i][h];
            next[i][h] = j;
            len[j][h] = len[i][h];
            len[i][h] = 1;
            return rightCnt + 1;
        } else {
            int downCnt = addNode(i, h - 1, j);
            if (h > level[j]) {
                len[i][h]++; // 当前层高于新节点的最大层数
            } else {
                next[j][h] = next[i][h];
                next[i][h] = j;
                len[j][h] = len[i][h] - downCnt;
                len[i][h] = downCnt + 1;
            }
            return rightCnt + downCnt;
        }
    }

    public static void removeCount(int i, int h, int num) {
        while (next[i][h] != 0 && key[next[i][h]] < num) {
            i = next[i][h];
        }
        if (h == 1) {
            count[next[i][h]]--;
        } else {
            removeCount(i, h - 1, num);
        }
        len[i][h]--;
    }

    public static void removeNode(int i, int h, int j) {
        if (h < 1) {
            return;
        }
        while (next[i][h] != 0 && key[next[i][h]] < key[j]) {
            i = next[i][h];
        }
        if (h > level[j]) {
            len[i][h]--;
        } else {
            next[i][h] = next[j][h];
            len[i][h] += len[j][h] - 1;
        }
        removeNode(i, h - 1, j);
    }

        // 获取元素出现次数
    public int getCount(int num) {
        int j = find(1, MAXL, num);
        return j != 0 ? count[j] : 0;
    }

    // 找到小于num的最大元素(前驱)
    public Integer lower(int num) {
        int i = 1;
        int h = MAXL;
        int res = Integer.MIN_VALUE;
        
        while (h >= 1) {
            while (next[i][h] != 0 && key[next[i][h]] < num) {
                i = next[i][h];
                res = key[i]; // 更新候选值
            }
            h--;
        }
        return res != Integer.MIN_VALUE ? res : null;
    }

    // 找到大于num的最小元素(后继)
    public Integer higher(int num) {
        int i = 1;
        int h = MAXL;
        
        while (h >= 1) {
            while (next[i][h] != 0 && key[next[i][h]] <= num) {
                i = next[i][h];
            }
            h--;
        }
        return next[i][1] != 0 ? key[next[i][1]] : null;
    }

    // 范围查询 [left, right]
    public List<Integer> range(int left, int right) {
        List<Integer> result = new ArrayList<>();
        int i = findLowerBound(1, MAXL, left);
        
        while (i != 0 && key[i] <= right) {
            for (int c = 0; c < count[i]; c++) {
                result.add(key[i]);
            }
            i = next[i][1];
        }
        return result;
    }

    // 辅助方法:找到第一个>=left的节点
    private int findLowerBound(int i, int h, int left) {
        if (h == 0) return 0;
        
        while (next[i][h] != 0 && key[next[i][h]] < left) {
            i = next[i][h];
        }
        
        int candidate = findLowerBound(i, h-1, left);
        return candidate != 0 ? candidate : next[i][h];
    }

}

http://www.niftyadmin.cn/n/5865998.html

相关文章

MFC学习笔记-1

一、编辑框和按钮 //.h文件private:CString str;//给窗口类加了一个变量&#xff08;定义一个成员变量&#xff09;&#xff0c;关联到IDC_EDIT1中&#xff08;要在实现中关联&#xff0c;源文件文件夹中&#xff09;CString str2;//接收button2&#xff0c;和IDC_EDIT2绑定 p…

Spring DIIoC

一.IoC 1.简介 什么是IoC&#xff1f;IoC&#xff0c;全称 Inversion of Control&#xff0c;控制反转。IoC是Spring的核心思想&#xff0c;Spring是⼀个“控制反转”的容器。 如果我们需要一个对象&#xff0c;正常来说我们是通过new一个对象&#xff0c;这个时候我们依赖的…

记录一下_treafik使用Gateway-APi使用的细节参数

一、这里说一下treafik最大的容易走入圈套的地方。 1、treafik默认不是hostnetwork模式。为了暴露自己出来它有一个LB类型的SVC。 这里的External_ip为我的节点IP&#xff0c;因为使用了k3s自带的LB&#xff0c;这个SVC就很容易绕进去。 1、第一个这个LB的作用是为了暴露treafi…

基于C#+SQL Server设计与实现的教学管理信息系统

教学管理信息系统 1、实验内容&#xff1a; 大学同时开设多门课程。每门课程有一个主讲教师&#xff0c;有多名学生选修&#xff1b;一个学生可选修多门课程并获得相应的学分和成绩&#xff1b;上课的基本单位是“次”&#xff08;一次 2 学时&#xff09;&#xff0c;每一次…

若依前后端分离框架修改3.8.9版本(重点在安全框架讲解与微信小程序登录集成)

若依模板改造&#xff08;3.8.9&#xff09; 1、基础改造 下载代码 从[RuoYi-Vue: &#x1f389; 基于SpringBoot&#xff0c;Spring Security&#xff0c;JWT&#xff0c;Vue & Element 的前后端分离权限管理系统&#xff0c;同时提供了 Vue3 的版本](https://gitee.co…

Flutter 实现抖音风格底部导航栏

在移动应用开发中&#xff0c;良好的导航设计对用户体验至关重要。抖音作为一款现象级应用&#xff0c;其底部导航设计简洁直观&#xff0c;极具吸引力。本文将详细介绍如何使用 Flutter 开发一个类似抖音风格的底部导航栏&#xff0c;帮助开发者打造兼具美观与实用的导航界面。…

找不到依赖项 <…> (Maven)

IDEA 的 build 操作和 maven 的 build 操作是分开的 重新加载 Maven 项目

RabbitMQ教程超详细(零基础入门有空更新)

1:入门第一步就是闲着没事看看RabbitMQ官网,更新什么。 这是官网地址 RabbtiMQ官网 2:这是基于erlang开发的,所以需要erlang环境。 优先下载环境 Releases rabbitmq/erlang-rpm GitHub 3:下载RabbitMQ安装包 RabbtiMQ安装包 4:上传自己的服务器上面 懂得人已经…