【每日算法】Day 16-1:跳表(Skip List)——Redis有序集合的核心实现原理(C++手写实现)

news2025/4/9 13:18:08

解锁O(log n)高效查询的链表奇迹!今日深入解析跳表的数据结构设计与实现细节,从基础概念到Redis级优化策略,彻底掌握这一平衡树的优雅替代方案。


一、跳表核心思想

跳表(Skip List) 是一种基于多层有序链表的概率型数据结构,核心特性:

  1. 多层结构:包含L0(完整数据层)到Lh(顶层索引层)

  2. 快速搜索:利用高层索引实现二分查找式跳跃

  3. 动态平衡:通过随机层数维持高效查询性能

与平衡树的对比优势:

特性跳表红黑树
实现复杂度简单(无需旋转操作)复杂(需维护平衡)
范围查询效率O(log n) + O(m)O(log n + m)
并发性能更易实现锁细粒度控制全局重平衡影响并发
内存占用额外指针空间(约2倍)平衡信息存储

二、跳表节点定义

struct SkipListNode {
    int val;
    vector<SkipListNode*> next; // 多层后继指针
    SkipListNode(int v, int level) : val(v), next(level, nullptr) {}
};

三、跳表完整实现(C++)

1. 基础结构
class SkipList {
private:
    const float P = 0.25;     // 节点晋升概率
    int maxLevel = 16;        // 最大层数限制
    int curLevel = 0;         // 当前最高层
    SkipListNode* head;       // 头节点(哑节点)

    // 随机生成节点层数
    int randomLevel() {
        int level = 1;
        while ((rand() % 100) < P*100 && level < maxLevel)
            level++;
        return level;
    }

public:
    SkipList() {
        head = new SkipListNode(INT_MIN, maxLevel);
    }
    
    ~SkipList() {
        // 层序遍历销毁所有节点(代码略)
    }
};
2. 搜索操作
bool search(int target) {
    SkipListNode* curr = head;
    for (int i = curLevel-1; i >= 0; --i) {
        while (curr->next[i] && curr->next[i]->val < target) {
            curr = curr->next[i];
        }
    }
    curr = curr->next[0];
    return curr && curr->val == target;
}
3. 插入操作
void add(int num) {
    vector<SkipListNode*> update(maxLevel, head);
    SkipListNode* curr = head;
    
    // 记录每层需要更新的节点
    for (int i = curLevel-1; i >= 0; --i) {
        while (curr->next[i] && curr->next[i]->val < num) {
            curr = curr->next[i];
        }
        update[i] = curr;
    }
    
    // 创建新节点
    int newLevel = randomLevel();
    if (newLevel > curLevel) {
        for (int i = curLevel; i < newLevel; ++i)
            update[i] = head;
        curLevel = newLevel;
    }
    
    SkipListNode* newNode = new SkipListNode(num, newLevel);
    for (int i = 0; i < newLevel; ++i) {
        newNode->next[i] = update[i]->next[i];
        update[i]->next[i] = newNode;
    }
}
4. 删除操作
bool erase(int num) {
    vector<SkipListNode*> update(maxLevel, nullptr);
    SkipListNode* curr = head;
    
    // 定位待删除节点
    for (int i = curLevel-1; i >= 0; --i) {
        while (curr->next[i] && curr->next[i]->val < num) {
            curr = curr->next[i];
        }
        update[i] = curr;
    }
    
    curr = curr->next[0];
    if (!curr || curr->val != num) return false;
    
    // 更新各层指针
    for (int i = 0; i < curLevel; ++i) {
        if (update[i]->next[i] != curr) break;
        update[i]->next[i] = curr->next[i];
    }
    
    // 更新当前最高层
    while (curLevel > 1 && head->next[curLevel-1] == nullptr)
        curLevel--;
    
    delete curr;
    return true;
}

四、Redis的跳表优化策略

1. 特殊设计要点
  • 晋升概率P=1/4:平衡空间与时间效率

  • 最大层数=32:足够支持2^64元素的理论需求

  • ZSKIPLIST_MAXLEVEL:动态调整最高层数

  • 双向指针:支持反向遍历(Redis 5.0+)

2. 存储结构图示
Redis跳表节点结构:
+------------+-----------+-------+-------+-----+-------+
| 成员对象   | 分值(score) | backward | level[] | ... |
+------------+-----------+-------+-------+-----+-------+

五、大厂真题实战

真题1:设计排行榜系统(某大厂2024面试)

需求:
实时维护玩家分数排名,支持:

  1. 更新玩家分数

  2. 查询Top N玩家

  3. 查询玩家排名

跳表解法:

class Leaderboard {
private:
    struct Node {
        int playerId;
        int score;
        // 重载比较运算符
        bool operator<(const Node& other) const {
            return score > other.score; // 按分数降序
        }
    };
    SkipList<Node> skipList;
    unordered_map<int, SkipListNode<Node>*> cache;

public:
    void addScore(int playerId, int score) {
        if (cache.count(playerId)) {
            auto node = cache[playerId];
            int oldScore = node->val.score;
            skipList.erase({playerId, oldScore});
            score += oldScore;
        }
        auto newNode = skipList.add({playerId, score});
        cache[playerId] = newNode;
    }
    
    vector<int> top(int K) {
        vector<int> res;
        auto curr = skipList.head->next[0];
        while (K-- && curr) {
            res.push_back(curr->val.playerId);
            curr = curr->next[0];
        }
        return res;
    }
};
真题2:时间序列数据库索引(某大厂2023笔试)

需求:
高效查询时间范围内的数据点
跳表变种设计:

  • 将时间戳作为排序键

  • 在高层索引中存储时间区间统计量(如最大值/最小值)

  • 范围查询时利用高层索引快速定位起始点


六、复杂度与优化对比

操作时间复杂度空间复杂度优化方向
插入平均O(log n)O(n)调整晋升概率P
删除O(log n)O(n)延迟删除优化
查询O(log n)O(n)增加高层索引密度
范围查询O(log n + m)O(n)双向指针优化

七、常见误区与调试技巧

  1. 层数分配不均:随机数生成器质量影响性能(建议使用MT19937)

  2. 指针未初始化:新节点next数组需全部置空

  3. 内存泄漏:需分层遍历释放所有节点

  4. 调试技巧

    • 可视化打印跳表结构

    • 为节点添加唯一ID辅助调试

    • 边界测试(空表/单节点/连续插入相同值)


进阶学习资源:

  1. Redis源码src/t_zset.c中的zskiplist实现

  2. 《算法导论》跳表复杂度证明

  3. Paper: "Skip Lists: A Probabilistic Alternative to Balanced Trees"

LeetCode真题训练:

  • 1206. 设计跳表

  • 632. 最小区间(多指针+跳表优化)

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

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

相关文章

GO语言学习(14)GO并发编程

目录 &#x1f308;前言 1.goroutine&#x1f31f; 2.GMP模型&#x1f31f; 2.1 GMP的由来☀️ 2.2 什么是GMP☀️ 3.channel &#x1f31f; 3.1 通道声明与数据传输&#x1f4a5; 3.2 通道关闭 &#x1f4a5; 3.3 通道遍历 &#x1f4a5; 3.4 Select语句 &#x1f4…

【Audio开发二】Android原生音量曲线调整说明

一&#xff0c;客制化需求 客户方对于音量加减键从静音到最大音量十五个档位区域的音量变化趋势有定制化需求。 二&#xff0c;音量曲线调试流程 Android根据不同的音频流类型定义不同的曲线&#xff0c;曲线文件存放在/vendor/etc/audio_policy_volumes.xml或者default_volu…

spring-security原理与应用系列:HttpSecurity.filters

目录 AnyRequestMatcher WebSecurityConfig HttpSecurity AbstractInterceptUrlConfigurer AbstractAuthenticationProcessingFilter 类图 在前面的文章《spring-security原理与应用系列&#xff1a;securityFilterChainBuilders》中&#xff0c;我们遗留了一个问题&…

JVM生产环境问题定位与解决实战(六):总结篇——问题定位思路与工具选择策略

本文已收录于《JVM生产环境问题定位与解决实战》专栏&#xff0c;完整系列见文末目录 引言 在前五篇文章中&#xff0c;我们深入探讨了JVM生产环境问题定位与解决的实战技巧&#xff0c;从基础的jps、jmap、jstat、jstack、jcmd等工具&#xff0c;到JConsole、VisualVM、MAT的…

并行治理机制对比:Polkadot、Ethereum 与 NEAR

治理是任何去中心化网络的基础。它塑造了社区如何发展、如何为创新提供资金、如何应对挑战以及如何随着时间的推移建立信任。随着 Web3 的不断发展&#xff0c;决定这些生态系统如何做出决策的治理模型也在不断发展。 在最近的一集的【The Decentralized Mic】中, Polkadot 汇…

TDengine tar.gz和docker两种方式安装和卸载

下载地址 3.1.1.0 Linux版本 安装包 下载地址 3.1.1.0 docker 镜像 下载地址 3.1.1.0 Window客户端 1. 将文件上传至服务器后解压 tar -zxvf TDengine-server-3.1.1.0-Linux-x64.tar.gz 2. tar.gz安装 解压文件后&#xff0c;进入相应子目录&#xff0c;执行其中的 install.…

【STM32设计】基于STM32的智能门禁管理系统(指纹+密码+刷卡+蜂鸣器报警)(代码+资料+论文)

本课题为基于单片机的智能门禁系统&#xff0c;整个系统由AS608指纹识别模块&#xff0c;矩阵键盘&#xff0c;STM32F103单片机&#xff0c;OLED液晶&#xff0c;RFID识别模块&#xff0c;继电器&#xff0c;蜂鸣器等构成&#xff0c;在使用时&#xff0c;用户可以录入新的指纹…

java知识梳理(二)

一.lambda表达式 作用&#xff1a;Lambda 表达式在 Java 8 引入&#xff0c;主要用于简化匿名内部类的写法&#xff0c;特别是在函数式编程场景中&#xff0c;比如 函数式接口、流式 API&#xff08;Streams&#xff09;、并发编程等。它让 Java 代码更简洁、可读性更强&#x…

鸿蒙Flutter实战:20. Flutter集成高德地图,同层渲染

本文以同层渲染为例&#xff0c;介绍如何集成高德地图 完整代码见 Flutter 鸿蒙版 Demo 概述 Dart 侧 核心代码如下&#xff0c;通过 OhosView 来承载原生视图 OhosView(viewType: com.shaohushuo.app/customView,onPlatformViewCreated: _onPlatformViewCreated,creation…

AI辅助下基于ArcGIS Pro的SWAT模型全流程高效建模实践与深度进阶应用

目前&#xff0c;流域水资源和水生态问题逐渐成为制约社会经济和环境可持续发展的重要因素。SWAT模型是一种基于物理机制的分布式流域水文与生态模拟模型&#xff0c;能够对流域的水循环过程、污染物迁移等过程进行精细模拟和量化分析。SWAT模型目前广泛应用于流域水文过程研究…

尚语翻译图册翻译|专业图册翻译|北京专业翻译公司推荐|专业文件翻译报价

内容概要 尚语翻译公司聚焦多语种产品图册翻译的竞价推广服务&#xff0c;通过行业垂直化运营构建差异化竞争力。其核心服务覆盖机械制造、医疗器械、电子元件三大领域&#xff0c;依托ISO 17100认证的翻译流程和Trados术语管理系统&#xff0c;实现技术文档的精准转化。为提升…

LeetCode 解题思路 30(Hot 100)

解题思路&#xff1a; 递归参数&#xff1a; 生成括号的对数 n、结果集 result、当前路径 path、左括号数 open、右括号数 close。递归过程&#xff1a; 当当前路径 path 的长度等于 n * 2 时&#xff0c;说明已经生成有效括号&#xff0c;加入结果集。若左括号数小于 n&…

Java EE(18)——网络原理——应用层HTTP协议

一.初识HTTP协议 HTTP(HyperText Transfer Protocol&#xff0c;超文本传输协议)是用于在客户端&#xff08;如浏览器&#xff09;和服务器之间传输超媒体文档&#xff08;如HTML&#xff09;的应用层协议。 HTTP协议发展至今发布了多个版本&#xff0c;其中1.0&#xff0c;1.…

强大而易用的JSON在线处理工具

强大而易用的JSON在线处理工具&#xff1a;程序员的得力助手 在当今的软件开发世界中&#xff0c;JSON&#xff08;JavaScript Object Notation&#xff09;已经成为了数据交换的通用语言。无论是前端还是后端开发&#xff0c;我们都经常需要处理、验证和转换JSON数据。今天&a…

Qt笔记----》不同环境程序打包

文章目录 概要1、windows环境下打包qt程序2、linux环境下打包qt程序2.1、程序目录2.2、创建一个空文件夹2.3、添加依赖脚本2.4、打包过程2.4.1、添加程序依赖库2.4.2、添加Qt相关依赖库 概要 qt不同运行环境下打包方式&#xff1a;windows/linux 1、windows环境下打包qt程序 …

企业服务器备份软件,企业服务器备份的方法有哪些?

企业服务器备份需综合考虑数据量、业务连续性要求&#xff08;RTO/RPO&#xff09;、合规性及成本等因素。以下是分场景的工具和方法指南&#xff1a; 一、备份软件推荐 1. 80KM备份软件 80KM备份软件可以进行很复杂的备份方式&#xff0c;也可以内网对内网备份、还能内网的…

html5炫酷图片悬停效果实现详解

html5炫酷图片悬停效果实现详解 这里写目录标题 html5炫酷图片悬停效果实现详解项目介绍技术栈核心功能实现1. 页面布局2. 图片容器样式3. 炫酷悬停效果缩放效果倾斜效果模糊效果旋转效果 4. 悬停文字效果5. 性能优化6. 响应式设计 项目亮点总结 项目介绍 本文将详细介绍如何使…

机器学习的一百个概念(5)数据增强

前言 本文隶属于专栏《机器学习的一百个概念》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见[《机器学习的一百个概念》 ima 知识库 知识库广场搜索&…

在MCU工程中优化CPU工作效率的几种方法

在嵌入式系统开发中&#xff0c;优化 CPU 工作效率对于提升系统性能、降低功耗、提高实时性至关重要。Keil 作为主流的嵌入式开发工具&#xff0c;提供了多种优化策略&#xff0c;包括 关键字使用、内存管理、字节对齐、算法优化 等。本文将从多个方面介绍如何在 Keil 工程中优…

美团民宿 mtgsig 小程序 mtgsig1.2 分析

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 逆向分析 cp execjs.compile(open(民…