C++ 实现跳表

news2024/11/25 8:23:04

目录

1.什么是跳表-skiplist

 2.skiplist的效率如何保证?

3.skiplist的实现

4.skiplist跟平衡搜索树和哈希表的对比 


1.什么是跳表-skiplist

        

   skiplist 本质上也是一种查找结构,用于解决算法中的查找问题,跟平衡搜索树和哈希表的价值是一样的,可以作为key 或者 key/value 的查找模型。那么相比而言它的优势是什么的呢?这么等我们学习完它的细节实现,我们再来对比。skiplist是由 William Pugh 发明的,最早出现于他在 1990 年发表的论文《 Skip Lists: A Probabilistic Alternative to Balanced Trees 》。对细节感兴趣的同学可以下载论文原文来阅读。 skiplist,顾名思义,首先它是一个 list 。实际上,它是在有序链表的基础上发展起来的。如果是一个有序的链表,查找数据的时间复杂度是O(N)
        
 William Pugh 开始的优化思路:
1. 假如我们 每相邻两个节点升高一层,增加一个指针,让指针指向下下个节点 ,如下图 b 所示。这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半。由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了,需要比较的节点数大概只有原来的一半。
2. 以此类推,我们可以在第二层新产生的链表上,继续为每相邻的两个节点升高一层,增加一个指针,从而产生第三层链表。如下图c ,这样搜索效率就进一步提高了。
3. skiplist 正是受这种多层链表的想法的启发而设计出来的。实际上,按照上面生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似 二分查找 ,使得查找的时间复杂度可以降低到 O(log n) 。但是这个结构在插入删除数据的时候有很大的问题 ,插入或者删除一个节点之后,就会打乱上下相邻两层链表上节点个数严格的2:1 的对应关系。如果要维持这种对应关系,就必须把新插入的节点后面的所有节点(也包括新插入的节点)重新进行调整,这会让时间复杂度重新蜕化成O(n)

4. skiplist 的设计为了避免这种问题,做了一个大胆的处理,不再严格要求对应比例关系,而是
插入一个节点的时候随机出一个层数。这样每次插入和删除都不需要考虑其他节点的层数,
这样就好处理多了。细节过程入下图:

 2.skiplist的效率如何保证?

上面我们说到, skiplist 插入一个节点时随机出一个层数,听起来怎么这么随意,如何保证搜索时
的效率呢?这里首先要细节分析的是这个随机层数是怎么来的。一般跳表会设计一个最大层数maxLevel 的限制,其次会设置一个多增加一层的概率p 。那么计算这个随机层数的伪代码如下图:

Redis skiplist 实现中,这两个参数的取值为:
p = 1/4
maxLevel = 32
根据前面 randomLevel() 的伪码,我们很容易看出,产生越高的节点层数,概率越低。定量的分析
如下:
节点层数至少为 1 。而大于 1 的节点层数,满足一个概率分布。
节点层数恰好等于 1 的概率为 1-p
节点层数大于等于 2 的概率为 p ,而节点层数恰好等于 2 的概率为 p(1-p)
节点层数大于等于 3 的概率为 p^2 ,而节点层数恰好等于 3 的概率为 p^2*(1-p)
节点层数大于等于 4 的概率为 p^3 ,而节点层数恰好等于 4 的概率为 p^3*(1-p)

因此,一个节点的平均层数(也即包含的平均指针数目),计算如下:

现在很容易计算出:

  • p=1/2时,每个节点所包含的平均指针数目为2
  • p=1/4时,每个节点所包含的平均指针数目为1.33
  • 跳表的平均时间复杂度为 O(logN) ,这个推导的过程较为复杂,需要有一定的数据功底,有兴趣的 老铁,可以参考以下文章中的讲解:
铁蕾大佬的博客: Redis内部数据结构详解(6)——skiplist - 铁蕾的个人博客

 William_Pugh大佬的论文:ftp://ftp.cs.umd.edu/pub/skipLists/skiplists.pdf

3.skiplist的实现

        力扣

#include <iostream>
#include <vector>
#include <algorithm>
#include <stack>
#include <map>
#include <ctime>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <deque>
using namespace std;

void PrintVector2D(const vector<vector<int>> &array)
{

    for (const auto &row : array)
    {
        cout << "[";
        for_each(row.begin(), row.end(), [](int num)
                 { cout << num << " "; });
        cout << "]";
        cout << endl;
    }
}

struct ListNode
{
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

struct TreeNode
{
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(nullptr), right(nullptr) {}
};

struct SkiplistNode
{
    int _val;
    vector<SkiplistNode *> _nextV;

    SkiplistNode(int val, int level) : _val(val), _nextV(level, nullptr) {}
};

class Skiplist
{
    typedef SkiplistNode Node;

public:
    Skiplist()
    {
        srand(time(0));
        _head = new SkiplistNode(-1, 1);
    }

    bool search(int target)
    {

        Node *cur = _head;
        int level = _head->_nextV.size() - 1;

        while (level >= 0)
        {

            //目标值比下一个节点值要打,向右走
            //下一个节点是空(尾)节点,目标值比下一个节点要小,向下走
            if (cur->_nextV[level] && cur->_nextV[level]->_val < target)
            {
                cur = cur->_nextV[level];
            }
            else if (cur->_nextV[level] == nullptr || cur->_nextV[level]->_val > target)
            {
                --level;
            }
            else
            {
                return true;
            }
        }
        return false;
    }

    vector<Node *> FindPrevNode(int num)
    {

        Node *cur = _head;
        int level = _head->_nextV.size() - 1;

        vector<Node *> prevV(level + 1, _head);

        while (level >= 0)
        {
            //目标值比下一个节点值大,向右走

            if (cur->_nextV[level] && cur->_nextV[level]->_val < num)
            {
                cur = cur->_nextV[level];
            }

            else if (cur->_nextV[level] == nullptr || cur->_nextV[level]->_val >= num)
            {
                prevV[level] = cur;
                --level;
            }
        }
        return prevV;
    }

    void add(int num)
    {

        vector<Node *> prevV = FindPrevNode(num);
        int n = RandomLevel();

        Node *newnode = new Node(num, n);

        if (n > _head->_nextV.size())
        {
            _head->_nextV.resize(n, nullptr);
            prevV.resize(n, _head);
        }

        for (size_t i = 0; i < n; i++)
        {
            newnode->_nextV[i] = prevV[i]->_nextV[i];
            prevV[i]->_nextV[i] = newnode;
        }
    }

    bool erase(int num)
    {

        vector<Node *> prevV = FindPrevNode(num);

        //下一层下一个不是val,val 不再表中

        if (prevV[0]->_nextV[0] == nullptr || prevV[0]->_nextV[0]->_val != num)
        {
            return false;
        }

        else
        {
            Node *del = prevV[0]->_nextV[0];

            for (size_t i = 0; i < del->_nextV.size(); i++)
            {

                prevV[i]->_nextV[i] = del->_nextV[i];
            }

            delete del;

            int i = _head->_nextV.size() - 1;
            while (i >= 0)
            {

                if (_head->_nextV[i] == nullptr)
                    --i;
                else
                    break;
            }
            _head->_nextV.resize(i + 1);
            return true;
        }
    }

    int RandomLevel()
    {
        size_t level = 1;
        while (rand() <= RAND_MAX * _p && level < _maxLevel)
        {
            ++level;
        }
        return level;
    }

    void Print()
    {

        Node *cur = _head;
        while (cur)
        {
            printf("%2d\n", cur->_val);
            for (auto e : cur->_nextV)
            {
                printf("%2s", "\u2193");
            }
            printf("\n");
            cur = cur->_nextV[0];
        }
    }

private:
    Node *_head;
    size_t _maxLevel = 32;
    double _p = 0.25;
};

int main()
{

    system("pause");
    return 0;
}

4.skiplist跟平衡搜索树和哈希表的对比 

1. skiplist 相比平衡搜索树 (AVL 树和红黑树 ) 对比,都可以做到遍历数据有序,时间复杂度也差不多。skiplist 的优势是: a skiplist 实现简单,容易控制。平衡树增删查改遍历都更复杂。
b skiplist 的额外空间消耗更低。平衡树节点存储每个值有三叉链,平衡因子 / 颜色等消耗。skiplist中 p=1/2 时,每个节点所包含的平均指针数目为 2 skiplist p=1/4 时,每个节点所包含的平均指针数目为1.33
2. skiplist 相比哈希表而言,就没有那么大的优势了。相比而言 a 、哈希表平均时间复杂度是O(1),比 skiplist 快。 b 、哈希表空间消耗略多一点。 skiplist 优势如下: a 、遍历数据有序
b skiplist 空间消耗略小一点,哈希表存在链接指针和表空间消耗。 c 、哈希表扩容有性能损耗。d 、哈希表再极端场景下哈希冲突高,效率下降厉害,需要红黑树补足接力。

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

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

相关文章

计算机毕业论文内容参考|基于C的空中战机游戏设计与实现

文章目录 导文文章重点摘要前言绪论1课题背景2国内外现状与趋势3课题内容相关技术与方法介绍系统分析系统设计系统实现系统测试总结与展望1本文总结2后续工作展望导文 计算机毕业论文内容参考|基于C的空中战机游戏设计与实现 文章重点 摘要 本文将介绍基于C编程语言的空中战机…

【电影推荐系统】数据加载

目录 数据集 解释 movie.csv ratings.csv tag.csv 数据预处理 mongodb 将数据按照csv文件里的分割符进行分割&#xff0c;转换为DF Moive Rating Tag es 将mongo集合tag 根据mid tag > mid tags(tag1|tag2|tag3...) moive 添加一列 tags 导入后数据库信息 mong…

python爬虫_正则表达式获取天气预报并用echarts折线图显示

文章目录 ⭐前言⭐python re库&#x1f496; re.match函数&#x1f496; re.search函数&#x1f496; re.compile 函数 ⭐正则获取天气预报&#x1f496; 正则实现页面内容提取&#x1f496; echarts的天气折现图 ⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分…

SpringBoot使用EasyExcel批量导出500万数据

SpringBoot使用EasyExcel批量导出500万数据 说明excel版本比较EasyExcel介绍项目目录mysql对应表建表语句pom.xmlapplication.yml配置类启动类代码OrderInfo 实体类OrderInfoExcel excel模版标题类(EasyExcel需要使用这个)TestController控制层接口层TestServiceTestServiceImp…

L298N模块驱动2项4线步进电机的多种方法及其优缺点

摘要&#xff1a;本文将详细介绍L298N模块驱动2项4线步进电机的多种方法&#xff0c;并分析各种方法的优缺点。在实例程序中&#xff0c;将展示不同方法的代码示例&#xff0c;帮助读者理解并实际应用。 引言&#xff1a; 步进电机作为一种常用的电机类型&#xff0c;在许多嵌入…

估值 2 个月从 11 亿美元降到 3 亿美元,投资人清仓跑路,国产大模型创业遇冷...

图片来源&#xff1a;由无界 AI生成 创业未半&#xff0c;而中道崩殂。 6 月 29 日&#xff0c;美团发布公告以 20.65 亿元全资收购光年之外全部权益&#xff0c;距离光年之外正式营业刚过去 84 天。 这是目前中国大模型创业领域最大的收购案&#xff0c;光年之外也在 4 个月时…

HTML5 游戏开发实战 | 黑白棋

黑白棋&#xff0c;又叫反棋(Reversi)、奥赛罗棋(Othello)、苹果棋、翻转棋。黑白棋在西方和日本很流行。游戏通过相互翻转对方的棋子&#xff0c;最后以棋盘上谁的棋子多来判断胜负。黑白棋的棋盘是一个有88方格的棋盘。开始时在棋盘正中有两白两黑四个棋子交叉放置&#xff0…

观察级水下机器人第一次使用总结2023年6月

最近有个科研项目需要用到ROV&#xff0c;其合同三年之前就签订了&#xff0c;由于疫情的影响&#xff0c;一直没有执行。刚好我们的ROV也验收了&#xff0c;正好派上用场。因为属于ROV使用的菜鸟级&#xff0c;我们邀请厂家无锡智海张工和陈工&#xff0c;中海辉固ROV操作经验…

纵向越权-业务安全测试实操(32)

纵向越权 某办公系统普通用户权限越权提升为系统权限 服务器为鉴别客户端浏览器会话及身份信息,会将用户身份信息存储在 Cookie中, 并发送至客户端存储。攻击者通过尝试修改Cookie中的身份标识为管理员,欺骗服务器分 配管理员权限,达到垂直越权的目的,如图所示。 某办公系…

「原汤话原食」更名「记者下班」,一切才刚刚开始

大家好&#xff0c;我是《原汤话原食》的小黑。这可能是我最后一次这样介绍自己。 毕竟&#xff0c;以后&#xff0c;我就得说&#xff0c;我是《记者下班》的小黑了。 事情是这样的&#xff1a; 2023年7月5日&#xff0c;津津乐道播客网络旗下《原汤话原食》节目正式更名为《记…

Claude使用教程,解决Claude不能回复

Claude是ChatGPT最为有⼒的竞争对⼿之⼀&#xff0c;Claude 的研发公司是专注人工智能安全和研究的初创公司 Anthropic&#xff0c;由前 OpenAI 员工共同创立的。今年 3 月份 Anthropic 获得了谷歌 3 亿美元的投资&#xff0c;谷歌也因此获得其 10% 股份。 ⽬前可以通过官⽹加…

day29-Oracle

0目录 第一章 Oracle 1.1 Oracle表空间-创建&#xff1a; 1.2 Oracle表空间-删除&#xff1a; 1.3 Oracle常用用户&#xff08;内置&#xff09;&#xff1a;&#xff08;1&#xff09;sys 超级用户&#xff1a; 定义&#xff1a;它是Oracle中的超级账户&#xff0…

百度算法提前批 面试复盘

作者 | liu_sy 面试锦囊之面经分享系列&#xff0c;持续更新中 欢迎后台回复"面试"加入讨论组交流噢 写在前面 之前通过非定向内推提前批&#xff0c;简历一直处于筛选状态中&#xff0c;然后大概在牛客看到一个前辈所在部门&#xff08;推荐搜索&#xff09;招人&…

【docker】在windows下配置linux深度学习环境并开启ssh远程连接

liunux下配置深度学习程序方便&#xff0c;windows下用起来更习惯。 windows下直接利用虚拟机是不太容易对GPU进行虚拟&#xff0c;利用docker就可以。这里简单介绍了在win主机下如利用docker&#xff0c;配置虚拟机环境&#xff0c;并和主机开启ssh连接配置。 配置与系统要求…

Delta数据湖upsert调优---1000多列表的调优

背景 本文基于 spark 3.1.1 delta 1.0.0 目前在我们公司遇到了一个任务写delta&#xff08;主要是的upsert操作&#xff09;&#xff0c;写入的时间超过了6个小时&#xff0c;该spark主要的做的事情是&#xff1a; 一行数据变几百行开窗函数去重调用pivot函数 行列的转换&…

Linux 串口工具minicom

Linux minicom Linux中的Minicom是一个串口通信工具&#xff0c;用于与外部设备进行串口通信。它可以用于与嵌入式设备、调试设备、网络设备等进行通信和配置。 调试和配置串口设备&#xff1a;minicom可以用于连接和调试各种串口设备&#xff0c;如调制解调器、路由器、交换…

软件DevOps云化发展的趋势 【课程限时免费】

你了解什么是DevOps吗&#xff1f; 它是怎么诞生的&#xff1f; DevOps能做些什么&#xff1f; 相信对于DevOps的实践者和关注者来说&#xff0c;对它已经不陌生了&#xff0c;但是对于刚刚进入开发者领域不久的小伙伴应该并不清楚&#xff0c;下面就让小智带你一起了解DevO…

node初识

一、什么是node node官网&#xff1a;https://nodejs.cn/ Node.js是一个开源的、跨平台的JavaScript运行环境。它基于Chrome V8 JavaScript引擎&#xff0c;使得JavaScript可以在服务器端运行。Node.js具有事件驱动、非阻塞式I/O的特性&#xff0c;适用于开发高性能的网络应…

ue4_Dota总结 GameMode篇

一&#xff1a;框架设计 新建地图M01&#xff1b; 创建gamemode&#xff1b; 创建gamestate&#xff1b; 创建playercontroller&#xff1b; 创建hud&#xff1b; 创建pawn&#xff1b; 将gamemode设置为M01地图中&#xff1b;将gamestate/playercontroller/hud/pawn添加…

SourceTree 切换分支时提示框 OpenSSH助手验证失败

问题描述&#xff1a; 这是我找的别的图&#xff0c;我自己的图忘记截了&#xff0c;大概意思差不多&#xff0c;就是服务器验证失败&#xff1a; 解决办法 &#xff1a;以下3步 1、命令行输入 ssh-keygen 然后一直下一步&#xff0c;直到结束&#xff0c;密钥和公钥会…