当前位置: 首页 > article >正文

跳表(Skip List)查找算法详解

1、原理

跳表是一种概率型数据结构,通过多层有序链表实现高效查找,时间复杂度接近平衡树(O(log n))。其核心思想是通过层级索引加速搜索,结构类似火车时刻表的“快车-慢车”模式。

关键特性

  1. 多层链表
    • 第 0 层是完整的有序链表。
    • 上层链表是下层的“快速通道”,节点间隔逐渐增大。
  2. 随机层数:插入节点时随机生成层数(如抛硬币,50%概率上升一层)。
  3. 跳跃查找:从最高层开始搜索,若下一节点值大于目标,则“下降”到下层继续。

示例:
查找值 6 的路径(从顶层开始):

Level 3: 1 ---------------------------> 9

Level 2: 1 --------5--------> 9

Level 1: 1 ---3---5---7----> 9

Level 0: 1-2-3-4-5-6-7-8-9

2、性能分析

  • 时间复杂度
    • 查找/插入/删除:平均 O(log n),最坏 O(n)(取决于随机层数的分布)。
  • 空间复杂度:平均 O(n)(每层链表存储部分节点)。
  • 对比平衡树

    特性

    跳表

    平衡树(如AVL)

    实现复杂度

    简单

    复杂

    范围查询

    高效(顺序遍历底层链表)

    需要中序遍历

    并发支持

    更易实现锁分段

    较难

3、 适用场景

  1. 替代平衡树:需要高效查找且实现简单的场景(如 Redis 的有序集合)。
  2. 范围查询:查找区间内的所有值(如 [5, 10])。
  3. 动态数据:频繁插入和删除的场景(维护成本低于平衡树)。
  4. 内存数据库:适合内存中的高性能数据结构。

不适用场景

  • 对严格 O(log n) 最坏性能有要求的场景。
  • 内存极度受限(跳表空间开销略高于数组)。

4、代码实现

Python:

import random  
from typing import Optional  class SkipNode:  def __init__(self, val: int = -1, levels: int = 0):  self.val = val  self.next = [None] * levels  class SkipList:  def __init__(self, max_level: int = 16):  self.max_level = max_level  self.head = SkipNode(levels=self.max_level)  self.level = 0  # 当前最大层数  def _random_level(self) -> int:  level = 1  while random.random() < 0.5 and level < self.max_level:  level += 1  return level  def search(self, target: int) -> bool:  curr = self.head  for i in range(self.level - 1, -1, -1):  while curr.next[i] and curr.next[i].val < target:  curr = curr.next[i]  curr = curr.next[0]  return curr and curr.val == target  def add(self, num: int) -> None:  update = [self.head] * self.max_level  curr = self.head  # 查找插入位置并记录每层的前驱节点  for i in range(self.level - 1, -1, -1):  while curr.next[i] and curr.next[i].val < num:  curr = curr.next[i]  update[i] = curr  # 随机生成层数  new_level = self._random_level()  if new_level > self.level:  for i in range(self.level, new_level):  update[i] = self.head  self.level = new_level  # 创建新节点并更新指针  new_node = SkipNode(num, new_level)  for i in range(new_level):  new_node.next[i] = update[i].next[i]  update[i].next[i] = new_node  # 调用示例  
sl = SkipList()  
sl.add(3)  
sl.add(6)  
sl.add(1)  
print(sl.search(6))  # 输出: True  
print(sl.search(5))  # 输出: False  

golang:

package main  
import (  "fmt"  "math/rand"  "time"  
)  const maxLevel = 16  type SkipNode struct {  val  int  next []*SkipNode  
}  type SkipList struct {  head      *SkipNode  maxLevel  int  currLevel int  
}  func NewSkipList() *SkipList {  rand.Seed(time.Now().UnixNano())  head := &SkipNode{val: -1, next: make([]*SkipNode, maxLevel)}  return &SkipList{head: head, maxLevel: maxLevel, currLevel: 1}  
}  func (sl *SkipList) randomLevel() int {  level := 1  for rand.Float64() < 0.5 && level < sl.maxLevel {  level++  }  return level  
}  func (sl *SkipList) Search(target int) bool {  curr := sl.head  for i := sl.currLevel - 1; i >= 0; i-- {  for curr.next[i] != nil && curr.next[i].val < target {  curr = curr.next[i]  }  }  curr = curr.next[0]  return curr != nil && curr.val == target  
}  func (sl *SkipList) Add(num int) {  update := make([]*SkipNode, sl.maxLevel)  curr := sl.head  // 查找插入位置并记录前驱节点  for i := sl.currLevel - 1; i >= 0; i-- {  for curr.next[i] != nil && curr.next[i].val < num {  curr = curr.next[i]  }  update[i] = curr  }  // 生成随机层数  newLevel := sl.randomLevel()  if newLevel > sl.currLevel {  for i := sl.currLevel; i < newLevel; i++ {  update[i] = sl.head  }  sl.currLevel = newLevel  }  // 创建新节点并更新指针  newNode := &SkipNode{val: num, next: make([]*SkipNode, newLevel)}  for i := 0; i < newLevel; i++ {  newNode.next[i] = update[i].next[i]  update[i].next[i] = newNode  }  
}  // 调用示例  
func main() {  sl := NewSkipList()  sl.Add(3)  sl.Add(6)  sl.Add(1)  fmt.Println(sl.Search(6))  // true  fmt.Println(sl.Search(5))  // false  
}  

php:

<?php  
class SkipNode {  public $val;  public $next = array();  public function __construct($val, $levels) {  $this->val = $val;  $this->next = array_fill(0, $levels, null);  }  
}  class SkipList {  private $maxLevel = 16;  private $head;  private $currentLevel = 1;  public function __construct() {  $this->head = new SkipNode(-1, $this->maxLevel);  }  private function randomLevel() {  $level = 1;  while (mt_rand() / mt_getrandmax() < 0.5 && $level < $this->maxLevel) {  $level++;  }  return $level;  }  public function search($target) {  $curr = $this->head;  for ($i = $this->currentLevel - 1; $i >= 0; $i--) {  while ($curr->next[$i] !== null && $curr->next[$i]->val < $target) {  $curr = $curr->next[$i];  }  }  $curr = $curr->next[0];  return $curr !== null && $curr->val === $target;  }  public function add($num) {  $update = array_fill(0, $this->maxLevel, $this->head);  $curr = $this->head;  // 查找插入位置并记录前驱节点  for ($i = $this->currentLevel - 1; $i >= 0; $i--) {  while ($curr->next[$i] !== null && $curr->next[$i]->val < $num) {  $curr = $curr->next[$i];  }  $update[$i] = $curr;  }  // 生成随机层数  $newLevel = $this->randomLevel();  if ($newLevel > $this->currentLevel) {  for ($i = $this->currentLevel; $i < $newLevel; $i++) {  $update[$i] = $this->head;  }  $this->currentLevel = $newLevel;  }  // 创建新节点并更新指针  $newNode = new SkipNode($num, $newLevel);  for ($i = 0; $i < $newLevel; $i++) {  $newNode->next[$i] = $update[$i]->next[$i];  $update[$i]->next[$i] = $newNode;  }  }  
}  // 调用示例  
$sl = new SkipList();  
$sl->add(3);  
$sl->add(6);  
$sl->add(1);  
echo $sl->search(6) ? 'true' : 'false';  // 输出: true  
echo $sl->search(5) ? 'true' : 'false';  // 输出: false  
?>  

java:

import java.util.Arrays;  
import java.util.Random;  class SkipNode {  int val;  SkipNode[] next;  public SkipNode(int val, int levels) {  this.val = val;  this.next = new SkipNode[levels];  }  
}  public class SkipList {  private static final int MAX_LEVEL = 16;  private SkipNode head;  private int currentLevel;  private Random random;  public SkipList() {  this.head = new SkipNode(-1, MAX_LEVEL);  this.currentLevel = 1;  this.random = new Random();  }  private int randomLevel() {  int level = 1;  while (random.nextDouble() < 0.5 && level < MAX_LEVEL) {  level++;  }  return level;  }  public boolean search(int target) {  SkipNode curr = head;  for (int i = currentLevel - 1; i >= 0; i--) {  while (curr.next[i] != null && curr.next[i].val < target) {  curr = curr.next[i];  }  }  curr = curr.next[0];  return curr != null && curr.val == target;  }  public void add(int num) {  SkipNode[] update = new SkipNode[MAX_LEVEL];  Arrays.fill(update, head);  SkipNode curr = head;  // 查找插入位置并记录前驱节点  for (int i = currentLevel - 1; i >= 0; i--) {  while (curr.next[i] != null && curr.next[i].val < num) {  curr = curr.next[i];  }  update[i] = curr;  }  // 生成随机层数  int newLevel = randomLevel();  if (newLevel > currentLevel) {  for (int i = currentLevel; i < newLevel; i++) {  update[i] = head;  }  currentLevel = newLevel;  }  // 创建新节点并更新指针  SkipNode newNode = new SkipNode(num, newLevel);  for (int i = 0; i < newLevel; i++) {  newNode.next[i] = update[i].next[i];  update[i].next[i] = newNode;  }  }  // 调用示例  public static void main(String[] args) {  SkipList sl = new SkipList();  sl.add(3);  sl.add(6);  sl.add(1);  System.out.println(sl.search(6));  // true  System.out.println(sl.search(5));  // false  }  
}  

5. 核心逻辑

  1. 层级索引:上层链表作为快速通道,加速查找。
  2. 随机层数:插入时通过概率控制层数,平衡索引密度。
  3. 查找路径:从高层向底层逐级下降,缩小搜索范围。

6、优化与扩展

  1. 动态调整层数概率:根据数据量调整上升概率(如从 50% 调整为 1/4)。
  2. 支持重复值:在节点中增加计数器或链表存储相同值。
  3. 范围查询优化:记录每层的边界指针,快速定位区间。

7、总结

跳表通过多层索引概率平衡,在简单实现的同时达到高效操作,适合需要动态数据管理的场景(如 Redis 有序集合)。其代码实现比平衡树更简洁,且支持高效的范围查询,是替代传统复杂数据结构的优秀选择。

相关文章:

跳表(Skip List)查找算法详解

1、原理 跳表是一种概率型数据结构&#xff0c;通过多层有序链表实现高效查找&#xff0c;时间复杂度接近平衡树&#xff08;O(log n)&#xff09;。其核心思想是通过层级索引加速搜索&#xff0c;结构类似火车时刻表的“快车-慢车”模式。 关键特性&#xff1a; 多层链表&a…...

React从基础入门到高级实战:React 核心技术 - React 与 TypeScript:构建类型安全的应用

React 与 TypeScript&#xff1a;构建类型安全的应用 在现代前端开发中&#xff0c;TypeScript 因其强大的类型系统和编译时错误检查功能&#xff0c;已成为 React 开发者的热门选择。通过为代码添加类型定义&#xff0c;TypeScript 能够显著提升代码的健壮性、可维护性和团队…...

Django orm详解--组成部件

Django ORM 的核心部件可分为模型系统、查询系统、数据库后端和辅助工具四大类&#xff0c;每个部件负责不同的职责&#xff0c;共同实现对象与关系数据库的映射。以下是核心部件的分层解析&#xff1a; 一、模型系统&#xff08;Model System&#xff09; 1. 模型基类&#…...

Tomcat 使用与配置全解

一、 Tomcat简介 Tomcat服务器是Apache的一个开源免费的Web容器。它实现了JavaEE平台下部分技术规范&#xff0c;属于轻量级应用服务器。 1. Tomcat版本 Tomcat版本 JDK版本 Servlet版本 JSP版本 10.0.X 8 and later 5.0 3.0 9.0.x 8 and later 4.0 2.3 8.0.x 7…...

Chrome 开发中的任务调度与线程模型实战指南

内容 概述 快速入门指南 核心概念线程词典 线程任务优先使用序列而不是物理线程 发布并行任务 直接发布到线程池通过 TaskRunner 发布 发布顺序任务 发布到新序列发布到当前&#xff08;虚拟&#xff09;主题 使用序列代替锁将多个任务发布到同一线程 发布到浏览器进程中的主线…...

aws instance store 的恢复

1: aws instance store 要在launch instance 才可以创建,而且,通过snapshot 恢复后,instance store 里面的数据会丢失。 下面是创建instance store 的过程,和通过两种方式恢复,发现/etc/fstab 不同的写法,有的不能启动: [root@ip-xx ~]# lsblk NAME MAJ:MIN RM …...

从零开始创建 Vue 3 开发环境并构建第一个 Demo

Vue 3 是目前前端开发中非常流行的渐进式 JavaScript 框架。本文将手把手带你完成从环境搭建到运行一个基础 Vue 3 示例的全过程。 &#x1f4e6; 一、环境准备 1. 安装 Node.js Vue 项目依赖 Node.js 运行环境&#xff0c;请确保你的电脑已安装 Node.js&#xff08;建议使用…...

EasyRTC音视频实时通话助力微信小程序:打造低延迟、高可靠的VoIP端到端呼叫解决方案

一、方案概述​ 在数字化通信浪潮下&#xff0c;端到端实时音视频能力成为刚需。依托庞大用户生态的微信小程序&#xff0c;是实现此类功能的优质载体。基于WebRTC的EasyRTC音视频SDK&#xff0c;为小程序VoIP呼叫提供轻量化解决方案&#xff0c;通过技术优化实现低延迟通信&a…...

STM32 SPI通信(软件)

一、SPI简介 SPI&#xff08;Serial Peripheral Interface&#xff09;是由Motorola公司开发的一种通用数据总线四根通信线&#xff1a;SCK&#xff08;Serial Clock&#xff09;、MOSI&#xff08;Master Output Slave Input&#xff09;、MISO&#xff08;Master Input Slav…...

每日刷题c++

快速幂 #include <iostream> using namespace std; #define int long long int power(int a, int b, int p) {int ans 1;while (b){if (b % 2){ans * a;ans % p; // 随时取模}a * a;a % p; // 随时取模b / 2;}return ans; } signed main() {int a, b, p;cin >> a …...

(自用)Java学习-5.19(地址管理,三级联动,预支付)

1. 地址管理模块 地址展示 前端&#xff1a;通过 showAddress() 发起 Ajax GET 请求&#xff0c;动态渲染地址列表表格&#xff0c;使用 #{tag}、#{name} 等占位符替换真实数据。 后端&#xff1a; 控制器层调用 AddressService&#xff0c;通过 AddressMapper 查询用户地址数…...

【容器】docker使用问题处理

问题一、systemctl start docker启动报 ERROR: ZONE_CONFLICT: docker0 already bound to a zone 处理方法 firewall-cmd --permanent --zonedocker --change-interfacedocker0 systemctl restart firewalld 问题二、启动容器报 ptables failed/iptables: No chain/target/…...

ChemDraw 2023|Win英文|化学结构编辑器|安装教程

软件下载 【名称】&#xff1a;ChemDraw 2023 【大小】&#xff1a;1.34G 【语言】&#xff1a;英文界面 【安装环境】&#xff1a;Win10/Win11 【夸克网盘下载链接】&#xff08;务必手机注册&#xff09;&#xff1a; https://pan.quark.cn/s/320bcb67da80 【网站下载…...

Vue3实现提示文字组件

Vue3 实现一个文字提示组件&#xff08;Tooltip&#xff09; 文字提示&#xff08;Tooltip&#xff09;是前端开发中非常常见的组件&#xff0c;通常用于在用户悬停某个元素时显示额外的信息。 一、需求分析 我们要实现一个 Vue3 的文字提示组件&#xff0c;具备以下功能&…...

JAVA与C语言之间的差异(一)

一、代码习惯以及主函数 JAVA中{在使用的时候不要换行 public static void main(String[] args) {int[] array {1, 2, 3};for(int i 0; i < array.length; i){System.out.println(array[i] " ");}} 其次&#xff0c;以main函数为主函数&#xff1a; public …...

深入剖析 C 语言中的指针数组与数组指针

资料合集下载链接: ​​https://pan.quark.cn/s/472bbdfcd014​​ 在C语言中,指针是其强大和灵活性的核心。然而,围绕指针的概念有很多容易混淆的地方,其中“指针数组”和“数组指针”就是一对常见的“双胞胎”概念。它们名称相似,但含义和用法却大相径庭。 本文旨在清…...

4.1.1 Spark SQL概述

Spark SQL是Apache Spark的一个模块&#xff0c;专门用于处理结构化数据。它引入了DataFrame这一编程抽象&#xff0c;DataFrame是带有Schema信息的分布式数据集合&#xff0c;类似于关系型数据库中的表。用户可以通过SQL、DataFrames API和Datasets API三种方式操作结构化数据…...

【VSCode-Qt】Docker远程连接的项目UI文件在 VSCode 上无法预览

Docker远程连接的UI文件在 VSCode 上无法预览&#xff0c;通常是因为 VSCode 通过远程开发扩展&#xff08;Remote - SSH/Docker&#xff09;连接到 Docker 容器时&#xff0c;某些图形化功能未正确配置或支持。以下是可能原因和解决方案&#xff1a; 原因分析 X11 转发未配置…...

redis五种数据结构详解(java实现对应的案例)

一、简述 Redis是一款高性能的键值对存储数据库&#xff0c;它支持五种基本数据类型&#xff0c;分别是字符串(String)、列表(List)、哈希(Hash)、集合(Set)、有序集合(Sorted Set)。 二、五种基本数据类型 2.1 字符串(String) String是Redis最基本的类型&#xff0c;一个key对…...

Telnet 命令详解

Telnet 命令详解&#xff1a;从基础到实战应用 Telnet 是一种历史悠久的网络协议&#xff0c;广泛用于远程登录和管理设备。尽管如今更安全的 SSH 协议已逐渐取代其地位&#xff0c;但 Telnet 在特定场景下依然发挥着重要作用。本文将深入解析 Telnet 命令的参数、使用场景及注…...

深度解析新能源汽车结构与工作原理

一、核心系统架构 新能源汽车主要由三大核心系统构成&#xff1a; 电力驱动系统&#xff1a;包含永磁同步电机、电机控制器&#xff08;MCU&#xff09;及减速器&#xff0c;采用三合一集成设计实现轻量化。永磁同步电机通过电磁感应原理将电能转化为机械能&#xff0c;其效率可…...

React 生命周期与 Hook:从原理到实战全解析

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 持续学习&#xff0c;不断…...

OpenSSL 与 C++ 搭建一个支持 TLS 1.3 的服务器

好的&#xff0c;我们可以使用 OpenSSL 与 C 搭建一个支持 TLS 1.3 的服务器。下面是&#xff1a; ✅ 一、完整示例代码&#xff08;基于 OpenSSL&#xff09; 使用 C 和 OpenSSL 创建一个简单的 TCP TLS 服务器&#xff0c;支持 TLS 1.3。 ✅ 代码&#xff1a;tls_server.cp…...

HOW - 简历和求职面试宝典(六)

文章目录 1. 如何更好地认识自己?一、认清自己的实力二、明确求职方向三、认识求职岗位与自己的匹配度2. 如何判断公司是否合适自己?一、网站平台二、内部人员三、通过面试官1. 如何更好地认识自己? 一、认清自己的实力 我们经常会听到这样的话:我现在的工作做的好不开心…...

【机器学习基础】机器学习入门核心算法:逻辑回归(Logistic Regression)

机器学习入门核心算法&#xff1a;逻辑回归&#xff08;Logistic Regression&#xff09; 一、算法逻辑1.1 基本概念1.2 Sigmoid函数1.3 决策边界 二、算法原理与数学推导2.1 概率建模2.2 损失函数推导2.3 梯度下降优化2.4 正则化处理 三、模型评估3.1 常用评估指标3.2 ROC曲线…...

深入理解设计模式之命令模式

下面是一篇关于设计模式之命令模式&#xff08;Command Pattern&#xff09;的详细博客&#xff0c;并附有 Java 实现代码示例。 深入理解设计模式之&#xff1a;命令模式&#xff08;Command Pattern&#xff09; 一、什么是命令模式&#xff1f; 命令模式&#xff08;Comma…...

智能仓储落地:机器人如何通过自动化减少仓库操作失误?

仓库作业的速度和准确性至关重要&#xff0c;尤其是在当前对无差错、高效作业的要求达到前所未有的环境下。每一个错误&#xff0c;无论是物品放错位置还是库存差异&#xff0c;都会在供应链中产生连锁反应&#xff0c;造成延误、增加成本&#xff0c;并最终影响客户满意度。 …...

Android 架构演进之路:从 MVC 到 MVI,拥抱单向数据流的革命

在移动应用开发的世界里&#xff0c;架构模式的演进从未停歇。从早期的 MVC 到后来的 MVP、MVVM&#xff0c;每一次变革都在尝试解决前一代架构的痛点。而今天&#xff0c;我们将探讨一种全新的架构模式 ——MVI&#xff08;Model-View-Intent&#xff09;&#xff0c;它借鉴了…...

[低代码表单生成器设计基础]ElementUI中Layout布局属性Form表单属性详解

Layout 布局 ElementUI 的 Layout 布局系统基于 24 栏栅格设计&#xff0c;提供了灵活的响应式布局能力&#xff0c;适用于各种页面结构的构建。(CSDN) &#x1f4d0; 基础布局结构 ElementUI 的布局由 <el-row>&#xff08;行&#xff09;和 <el-col>&#xff0…...

数据结构7——二叉树

一、二叉树的定义与性质 1.定义 首先是树形结构&#xff0c;每个节点最多有2棵树&#xff0c;二叉树的子树有左右之分&#xff0c;不能颠倒。 2.性质 &#xff08;1&#xff09;二叉树的第i层,最多有2的&#xff08;i-1)次幂。 &#xff08;2&#xff09;深度为k&#xff0…...