跳跃表是一种特殊的单向有序链表,redis中zset在数据量大时就采用了这种数据结构。

添加,删除,查询的时间复杂度都为O(logn)

package algorithm.array_linked;import lombok.Data;import java.util.*;/*** @Author: M˚Haonan* @Date: 2022/1/20 10:54* @Description: 跳表* 跳跃表是一种特殊的有序链表,利用空间换时间,通过多层索引加快链表的检索速度** 设计上,采用了一种技巧,每一层都有一个空的哨兵节点,next为实际的元素或索引,down为下一层的哨兵节点。* 这样设计的好处是,当某个节点的level超过当前的totalLevel需要升一层时,前驱节点可以方便的指定为哨兵节点,直接在哨兵节点的next插入即可* indexHead指向最顶层的哨兵节点** 为了简化,本skipList中不支持重复元素,即score都不相同*/
@Data
public class SkipList<E> {//指向最高层索引链表的头节点,由于每层都有一个空的头结点null,因此这个指向最高层的nullprivate SkipNode<E> indexHead = new SkipNode<>(null, 0);private SkipNode<E> head = new SkipNode<>(null, Integer.MIN_VALUE);//一共的层级,包括数据链表层private int totalLevel;//限制跳跃表的最大层级private int maxLevel;//初始化层数private int initLevel;//计算节点是否需要上移一层private Random random = new Random();@Datastatic class SkipNode<E> {//下一个节点,如果该节点是索引节点,指向下一个索引节点private SkipNode<E> next;//如果该节点是索引节点,指向下一层原始节点private SkipNode<E> down;//得分,用来排序private int score;//存储的数据private E data;public SkipNode(E data, int score){this.data = data;this.score = score;}@Overridepublic String toString() {return "SkipNode{" +"score=" + score +'}';}}public SkipList(int initLevel, int maxLevel) {this.maxLevel = maxLevel;this.initLevel = initLevel;//初始化每一层的头节点,这里初始化完后,数据结构如下/*indexHead -> guard(null)guard(null)guard(null)*/SkipNode<E> curr = new SkipNode<>(null, Integer.MIN_VALUE);head.next = curr;for (int i = 1; i < initLevel; i++) {curr = createIndexNode(curr);}indexHead.next = curr;}/*** 利用抛硬币的方式,获取当前节点的层数,不超过最大level* @return*/private int getLevel() {int level = 1;while (random.nextInt(2) == 0 && level <= maxLevel) {level ++;}return level;}/*** 为当前节点创建下一层索引节点* @param node* @return*/private SkipNode<E> createIndexNode(SkipNode<E> node) {//得分相同SkipNode<E> indexNode = new SkipNode<>(null, node.getScore());//索引节点的下一层为传入的节点indexNode.down = node;return indexNode;}/*** 添加节点* @param node*/public void add(SkipNode<E> node) {SkipNode<E> curr = indexHead.next;//保存每层当前节点的prev,便于回溯时添加索引节点//下面整个while循环就是为了给这个list添加元素,获取插入的位置。由于往下层走的时候,此层要么走完,要么下一个节点的score大于要插入的节点//因此,当前位置一定是需要插入的位置,记录此时的prevList<SkipNode<E>> prevList = new ArrayList<>();while (curr != null) {if (curr.next == null) {//说明当前层走到尽头了,把curr指针往下层移动,并且需要记录当前层的prev节点到list中SkipNode<E> prev = curr;prevList.add(prev);curr = curr.down;continue;}while (curr.next != null && curr.next.score < node.score) {//当 当前score小于node时,需要继续往右寻找curr = curr.next;}if (curr.score < node.score) {//这种情况说明要么下一个节点是null,要么下一个节点的score>node.score,此时需要往下走,和上面一样,保存当前层的前驱节点SkipNode<E> prev = curr;prevList.add(prev);curr = curr.down;continue;}}//循环走完以后,说明已经找到了每一层的插入位置,从下往上依次插入每一层的节点//按层级从下往上插入节点SkipNode<E> last = node;int level = getLevel();int allowLevel = Math.min(level, maxLevel);//代表此时最顶端左边的节点,这个变量的作用是,当插入节点的level大于当前最高level时,需要往上插入一个哨兵节点,把节点索引插入哨兵节点之后SkipNode<E> top = indexHead.next;for (int i = 1; i <= allowLevel; i++) {int index = prevList.size() - i;if (index >= 0) {//说明有prev节点,只需要在prev节点之后插入即可SkipNode<E> prev = prevList.get(index);last.next = prev.next;prev.next = last;last = createIndexNode(last);}else {//说明level已经超过当前的totalLevel,需要往上新插入哨兵节点和索引节点SkipNode<E> temp = new SkipNode<>(null, Integer.MIN_VALUE);temp.next = last;temp.down = top;top = temp;last = createIndexNode(last);indexHead.next = temp;}}//更新最大层级totalLevel = Math.max(totalLevel, allowLevel);}/*** 检索指定score的节点* @param score*/public E search(int score) {SkipNode<E> curr = indexHead.next;while (curr != null) {if (curr.next == null) {curr = curr.down;continue;}while (curr.next != null && curr.next.score <= score) {curr = curr.next;}if (curr.score == score) {break;}curr = curr.down;}if (curr == null) {return null;}while (curr.down != null) {curr = curr.down;}return curr.data;}@Overridepublic String toString() {return "SkipList{" +"totalLevel=" + totalLevel +'}';}public String print() {SkipNode<E> currHead = indexHead.next;int i = 0;while (currHead != null){int curr = Math.max(initLevel, totalLevel) - i;SkipNode<E> node = currHead.next;System.out.println("第" + curr + "层数据如下:");i++;while (node != null) {System.out.print(node.getData() + ": score = " + node.score + "\t");node = node.next;}currHead = currHead.down;System.out.println("--------------------------------");}return "";}public String print2() {SkipNode<E> curr = head.next;while (curr != null) {System.out.print(curr.getData() + "\t");curr = curr.next;}return "";}public static void main(String[] args) {//        SkipList<Integer> skipList = new SkipList<>(4, 20);
//        long start = System.currentTimeMillis();
//        int num = 2000000;
//        for (int i = 0; i < num; i++) {//            skipList.add(new SkipNode<>(i, i));
//        }
//        System.out.println(skipList.getTotalLevel());
//        //添加10w个元素耗时:33
//        System.out.println("添加" + num + "个元素耗时:" + (System.currentTimeMillis() - start) + "ms");
//        //检索速度
//        start = System.currentTimeMillis();
//        System.out.println(skipList.search(567));
//        System.out.println(num + "个元素查询耗时:" + (System.currentTimeMillis() - start) + "ms");
//        System.out.println(skipList.print());test1();}public static void test1() {SkipList<Integer> skipList = new SkipList<>(4, 20);Random random = new Random();long start = System.currentTimeMillis();int num = 1000000;Set<Integer> nums = new HashSet<>();for (int i = 0; i < num; i++) {int curr;do {curr = random.nextInt(10000000);} while (!nums.add(curr));skipList.add(new SkipNode<>(curr, curr));}skipList.add(new SkipNode<>(124214, 124214));num = nums.size();System.out.println(skipList.getTotalLevel());//添加1000000个元素耗时:1326msSystem.out.println("添加" + num + "个元素耗时:" + (System.currentTimeMillis() - start) + "ms");//检索速度 0msstart = System.currentTimeMillis();System.out.println(skipList.search(124214));System.out.println(num + "个元素查询耗时:" + (System.currentTimeMillis() - start) + "ms");
//        skipList.print();}
}

跳跃表之java实现相关推荐

  1. 跳跃表(Skip list)原理与java实现

    转载自 [算法导论33]跳跃表(Skip list)原理与java实现 Skip list是一个用于有序元素序列快速搜索的数据结构,由美国计算机科学家William Pugh发明于1989年.它的效率 ...

  2. 【算法导论33】跳跃表(Skip list)原理与java实现

    Skip list是一个用于有序元素序列快速搜索的数据结构,由美国计算机科学家William Pugh发明于1989年.它的效率和红黑树以及 AVL 树不相上下,但实现起来比较容易.作者William ...

  3. 《漫画算法2》源码整理-3 二分查找 跳跃表

    二分查找 public class BinarySearch {public static int binarySearch(int[] array, int target) {//查找范围起点int ...

  4. Redis底层原理之跳跃表

    1. 什么是跳跃表? 增加了向前指针的链表叫作跳表.跳表全称叫做跳跃表.跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表.跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查 ...

  5. Redis中的zset原理以及用Java实现跳跃表

    准备工作 先在Redis官网下载最新的稳定版本6.2.按照官网给出的安装指南到Linux服务器上安装. zadd调用过程 redis/src/server.c 文件中定义了所有命令运行要调用的方法.z ...

  6. java 跳跃表_c++实现跳跃表(Skip List)的方法示例

    前言 Skip List是一种随机化的数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要O(log n)平均时间).基本上,跳跃列表是对有序的链表增加上附加的前进链接,增加是以随机 ...

  7. Redis: 跳跃表

    总结: 基于链表的查询由于不能使用二分查找算法,因此需要挨个遍历链表元素对比定位.插入和删除操作虽然只需要进行指针的变换,但首先还是要定位到插入位置,因此当链表的数据量比较大的时候,就会出现效率很低, ...

  8. 常见索引结构—跳跃表

    原文作者:JeemyJohn 原文地址:跳跃表的原理及实现 推荐阅读:漫画算法:什么是跳跃表? 1. 跳跃表的原理 学过数据结构的都知道,在单链表中查询一个元素的时间复杂度为O(n),即使该单链表是有 ...

  9. 跳跃表 mysql_跳跃表原理与实践

    ---恢复内容开始--- 像是redis中有序集合就使用到了跳跃表. 场景:商品总数量有几十万件,对应数据库商品表的几十万条记录.需要根据不同字段正序或者倒叙做精确或全量查询,而且性能有硬性要求. 如 ...

最新文章

  1. JQ中$(window).load和$(document).ready()使用,区别与执行顺序
  2. 订单信息修改java模型图,java毕业设计_springboot框架的物流运输管理系统订单管理...
  3. python win32模块详解_python模块:win32com用法详解
  4. CSP2019洛谷P5666:树的重心
  5. Navicat连接Oracle数据库失败,提示无效的用户名和密码(Invalid username and password)
  6. Python3抓取糗百、不得姐、kanqu.com
  7. Matlab绘制柱状图, 设置figure的最大最小值
  8. 目前国内最快最稳定的DNS
  9. 三星android安装到sd卡,三星怎么安装sd卡 三星手机怎么安装sim卡
  10. day03-搭建项目
  11. Java面试基础(二)
  12. Spire.PDF:如何添加、删除PDF页面以及自定义文档属性
  13. 《天赋》:第一章 天赋
  14. 动态规划之背包问题 01背包
  15. nginx【30】listen指令的用法
  16. 利用qwinsta和rwinsta察看连接到一个机器的连接数
  17. 科创板|赛诺医疗申联生物海天瑞声3公司本月31日上会
  18. 麒麟A1手表升级鸿蒙,HUAWEI WATCH GT2首发体验: 首款麒麟A1的手表有多神?14天续航小意思...
  19. 如何优雅的抄袭代码?天下代码一大抄,这才是正确的姿势
  20. 使用nat123远程ssh连接WSL Ubuntu系统

热门文章

  1. Java输出List
  2. SCJP复习规划for 1.4
  3. POJ2676 (数独问题 + DLX + 状态优化顺序)
  4. iOS 展示播放视频画面
  5. 春耕有时,2016触控科技高校认证讲师训练营华丽启动
  6. 玩客云|赚钱宝3代|魔百盒 Armbian Docker 空间不足 增加U盘容量
  7. 赚钱宝 mysql_狂雨小说CMS 1.1.1 最新版源码 基于ThinkPHP5.1+MySQL的开发
  8. 【日语】日语国际贸易用语
  9. Oracle数据库--Oracle作业基础知识整合
  10. python rename variables in vsc_vsc: Visual Studio Code Guide[Simple Chinese][简体中文]