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

news2025/6/1 0:42:47

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 有序集合)。其代码实现比平衡树更简洁,且支持高效的范围查询,是替代传统复杂数据结构的优秀选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2392118.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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…

aws instance store 的恢复

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

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 …

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

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

4.1.1 Spark SQL概述

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

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

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

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

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

【机器学习基础】机器学习入门核心算法:逻辑回归(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曲线…

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

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

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

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

从“被动养老”到“主动健康管理”:平台如何重构代际关系?

在老龄化与数字化交织的背景下&#xff0c;代际关系的重构已成为破解养老难题的关键。 传统家庭养老模式中&#xff0c;代际互动多表现为单向的“赡养-被赡养”关系。 而智慧养老平台的介入&#xff0c;通过技术赋能、资源整合与情感连接&#xff0c;正在推动代际关系向“协作…

贪心算法应用:最大匹配问题详解

Java中的贪心算法应用:最大匹配问题详解 贪心算法是一种在每一步选择中都采取当前状态下最优的选择,从而希望导致结果是全局最优的算法策略。在Java中,贪心算法可以应用于多种问题,其中最大匹配问题是一个经典的应用场景。下面我将从基础概念到具体实现,全面详细地讲解贪…

爬虫IP代理效率优化:策略解析与实战案例

目录 一、代理池效率瓶颈的根源分析 二、六大核心优化策略 策略1&#xff1a;智能IP轮换矩阵 策略2&#xff1a;连接复用优化 策略3&#xff1a;动态指纹伪装 策略4&#xff1a;智能重试机制 三、典型场景实战案例 案例1&#xff1a;电商价格监控系统 案例2&#xff1a…

豆瓣电视剧数据工程实践:从爬虫到智能存储的技术演进(含完整代码)

通过网盘分享的文件&#xff1a;资料 链接: https://pan.baidu.com/s/1siOrGmM4n-m3jv95OCea9g?pwd4jir 提取码: 4jir 1. 引言 1.1 选题背景 在影视内容消费升级背景下&#xff0c;豆瓣电视剧榜单作为国内最具影响力的影视评价体系&#xff0c;其数据价值体现在&#xff1a…

基于微信小程序的漫展系统的设计与实现

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

基于Web的分布式图集管理系统架构设计与实践

引言&#xff1a;为什么需要分布式图集管理&#xff1f; 在现代Web图形应用中&#xff0c;纹理图集&#xff08;Texture Atlas&#xff09;技术是优化渲染性能的关键手段。传统的图集制作流程通常需要美术人员使用专业工具&#xff08;如TexturePacker&#xff09;离线制作&am…

mysql执行sql语句报错事务锁住

报错情况 1205 - Lock wait timeout exceeded; try restarting transaction先找出长时间运行的事务 SELECT * FROM information_schema.INNODB_TRX ORDER BY trx_started ASC;终止长时间运行的事务 KILL [PROCESS_ID];

Java消息队列应用:Kafka、RabbitMQ选择与优化

Java消息队列应用&#xff1a;Kafka、RabbitMQ选择与优化 在Java应用领域&#xff0c;消息队列是实现异步通信、应用解耦、流量削峰等重要功能的关键组件。Kafka和RabbitMQ作为两种主流的消息队列技术&#xff0c;各有特点和适用场景。本文将深入探讨Kafka和RabbitMQ在Java中的…