【数据结构】跳表SkipList代码解析(C++)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 【数据结构】跳表SkipList代码解析(C++)

跳表SkipList解析

什么是跳表

这里不做介绍,详见:

代码解析

主要理解点

  • 先来张图

层次感

  1. 各个节点是如何相连接(关联)的?

    • 通过每个节点的forward数组,forward数组存储当前节点,在每一层的下一个节点。
    • 以头节点为例,头结点的forward存储的是每一层的第一个节点。然后通过第一个节点的forward[level],拿到该层的后面元素...... 以次类推,即可遍历该层所有节点。
    • 与普通单链表的区别,我们可以大概理解为,上面多了几层简化的结果,如上面动画所示。
  2. update存的是什么?

    • 每层中最后一个key小于要插入节点key的节点。
  3. 节点生成的level是多少,该节点就从0层到level层一直都出现。
  4. 其它详见具体代码中的注释

相关类&成员函数

节点类Node

template<typename K, typename V> 
class Node {
public:
    // 无参构造
    Node() {} 
    // 有参构造-最后一个参数为层数
    Node(K k, V v, int); 
    // 析构
    ~Node();
    // 拿到当前节点key值
    K get_key() const;
    // 拿到当前节点value值
    V get_value() const;
    // 设置value值
    void set_value(V); 
    // forward-存储当前结点在i层的下一个结点。
    Node<K, V> **forward;
    //所在层级
    int node_level;
private:
    K key;
    V value;
};

跳表类SkipList

template <typename K, typename V> 
class SkipList {

public: 
    // 有参构造-参数为最大层数
    SkipList(int);
    // 析构
    ~SkipList();
    // 生成随机层数
    int get_random_level();
    // 创建节点-最后一个参数为所在层数
    Node<K, V>* create_node(K, V, int);
    // 插入数据
    int insert_element(K, V);
    // 打印数据-每层都打印
    void display_list();
    // 查找元素
    bool search_element(K);
    // 删除元素
    void delete_element(K);
    // 存入文件
    void dump_file();
    // 加载文件
    void load_file();
    // 返回节点个数
    int size();

private:
    // 从文件中的一行读取key和value
    void get_key_value_from_string(const std::string& str, std::string* key, std::string* value);
    // 是否为有效的string
    bool is_valid_string(const std::string& str);

private:    
    // 该跳表最大层数
    int _max_level;

    // 该跳表当前层数
    int _skip_list_level;

    // 跳表头节点
    Node<K, V> *_header;

    // 文件操作
    std::ofstream _file_writer;
    std::ifstream _file_reader;

    // 该跳表当前的元素数
    int _element_count;
};

详解insert函数

节点的插入遍历拿到update数组

Node<K, V> *update[_max_level+1];//使用_max_level+1开辟,使空间,肯定够,因为创建节点的时候,会对随机生成的key进行限制。
memset(update, 0, sizeof(Node<K, V>*)*(_max_level+1));  

// 从跳表左上角开始查找——_skip_list_level为当前所存在的最高的层(一共有多少层则需要+1,因为是从level=0层开始的)
for(int i = _skip_list_level; i >= 0; i--) {//控制当前所在层,从最高层到第0层

    //从每一层的最左边开始遍历,如果该节点存在并且,key小于我们要插入的key,继续在该层后移。
    while(current->forward[i] != NULL && current->forward[i]->get_key() < key) {//是不是继续往后面走
        current = current->forward[i]; //提示: forward存储该节点在当前层的下一个节点
    }
    update[i] = current;//保存
    //切换下一层
}

//将current指向第0层第一个key大于要插入节点key的节点。
//下面用current用来判断要插入的节点存key是否存在
//即,如果该key不存在的话,准备插入到update[i]后面。存在,则修改该key对应的value。
current = current->forward[0];

在这里插入图片描述


更新层次

// 为当前要插入的节点生成一个随机层数
int random_level = get_random_level();

//如果随机出来的层数大于当前链表达到的层数,注意:不是最大层,而是当前的最高层_skip_list_level。
//更新层数,更新update,准备在每层([0,random_level]层)插入新元素。
if (random_level > _skip_list_level) {
    for (int i = _skip_list_level+1; i < random_level+1; i++) {
        update[i] = _header;
    }
    _skip_list_level = random_level;
}

更新层次


插入节点

 // 创建节点
Node<K, V>* inserted_node = create_node(key, value, random_level);

// 插入节点
for (int i = 0; i <= random_level; i++) {
    //在每一层([0,random_level])
    //先将原来的update[i]的forward[i]放入新节点的forward[i]。
    //再将新节点放入update[i]的forward[i]。
    inserted_node->forward[i] = update[i]->forward[i];//新节点与后面相链接
    update[i]->forward[i] = inserted_node;//新节点与前面相链接
}
//std::cout << "Successfully inserted key:" << key << ", value:" << value << std::endl;
_element_count++;//元素总数++

插入节点


相关参考


相关文章
|
15天前
|
自然语言处理 编译器 Linux
|
20天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
57 10
|
18天前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
20天前
|
自然语言处理 编译器 Linux
告别头文件,编译效率提升 42%!C++ Modules 实战解析 | 干货推荐
本文中,阿里云智能集团开发工程师李泽政以 Alinux 为操作环境,讲解模块相比传统头文件有哪些优势,并通过若干个例子,学习如何组织一个 C++ 模块工程并使用模块封装第三方库或是改造现有的项目。
|
19天前
|
前端开发 JavaScript 开发者
揭秘前端高手的秘密武器:深度解析递归组件与动态组件的奥妙,让你代码效率翻倍!
【10月更文挑战第23天】在Web开发中,组件化已成为主流。本文深入探讨了递归组件与动态组件的概念、应用及实现方式。递归组件通过在组件内部调用自身,适用于处理层级结构数据,如菜单和树形控件。动态组件则根据数据变化动态切换组件显示,适用于不同业务逻辑下的组件展示。通过示例,展示了这两种组件的实现方法及其在实际开发中的应用价值。
27 1
|
23天前
|
存储 Java 开发者
Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效
【10月更文挑战第19天】在软件开发中,随着项目复杂度的增加,数据结构的组织和管理变得至关重要。Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,帮助开发者告别混乱,提升代码质量。
26 1
|
1月前
|
机器学习/深度学习 人工智能 算法
揭开深度学习与传统机器学习的神秘面纱:从理论差异到实战代码详解两者间的选择与应用策略全面解析
【10月更文挑战第10天】本文探讨了深度学习与传统机器学习的区别,通过图像识别和语音处理等领域的应用案例,展示了深度学习在自动特征学习和处理大规模数据方面的优势。文中还提供了一个Python代码示例,使用TensorFlow构建多层感知器(MLP)并与Scikit-learn中的逻辑回归模型进行对比,进一步说明了两者的不同特点。
63 2
|
18天前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
18天前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
1月前
|
存储 算法 索引
HashMap底层数据结构及其增put删remove查get方法的代码实现原理
HashMap 是基于数组 + 链表 + 红黑树实现的高效键值对存储结构。默认初始容量为16,负载因子为0.75。当存储元素超过容量 * 负载因子时,会进行扩容。HashMap 使用哈希算法计算键的索引位置,通过链表或红黑树解决哈希冲突,确保高效存取。插入、获取和删除操作的时间复杂度接近 O(1)。
29 0

热门文章

最新文章

推荐镜像

更多