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

什么是跳表

1. 什么是跳表?

直观理解

想象一下你在查阅一本很厚的词典:

  • 最笨的方法:从第一页开始一页一页翻找 → O(n)

  • 聪明的方法:先看目录,找到大概范围,再细找 → 这就是跳表的思想

跳表就是一种支持快速查找的有序链表,通过建立多级索引来加速查找过程。

2. 跳表的基本结构

2.1 普通的有序链表

text

1 -> 3 -> 5 -> 7 -> 9 -> 11 -> 13 -> 15

查找时间复杂度:O(n)

2.2 添加第一级索引

text

第一级索引:1 ------> 5 ------> 9 ------> 13|        |        |        |
底层链表:  1 -> 3 -> 5 -> 7 -> 9 -> 11 -> 13 -> 15

2.3 添加第二级索引

text

第二级索引:1 ---------------> 9|                 |
第一级索引:1 ------> 5 ------> 9 ------> 13|        |        |        |
底层链表:  1 -> 3 -> 5 -> 7 -> 9 -> 11 -> 13 -> 15

3. 跳表的核心思想

3.1 空间换时间

  • 额外空间:存储多级索引

  • 时间收益:查找效率从 O(n) 提升到 O(log n)

3.2 概率平衡

跳表不像平衡树那样需要严格的旋转操作,而是通过随机化来决定节点的高度。

4. 跳表的详细实现

4.1 节点结构

cpp

struct SkipListNode {int value;vector<SkipListNode*> next;  // 每层的下一个指针vector<SkipListNode*> prev;  // 双向跳表需要,单向可以省略SkipListNode(int val, int level) : value(val), next(level, nullptr) {}
};

4.2 跳表类框架

cpp

class SkipList {
private:SkipListNode* head;     // 头节点,有最大层数int maxLevel;           // 最大层数int currentLevel;       // 当前有效层数float probability;      // 晋升概率,通常为0.5// 随机生成节点层数int randomLevel() {int level = 1;while ((rand() / (float)RAND_MAX) < probability && level < maxLevel) {level++;}return level;}public:SkipList(int maxLvl = 16, float p = 0.5) : maxLevel(maxLvl), probability(p), currentLevel(1) {head = new SkipListNode(INT_MIN, maxLevel);  // 头节点值为最小值}// 查找、插入、删除等方法...
};

5. 核心操作详解

5.1 查找操作

查找值为 target 的节点:

cpp

bool search(int target) {SkipListNode* current = head;// 从最高层开始查找for (int i = currentLevel - 1; i >= 0; i--) {// 在当前层向前移动,直到下一个节点值大于等于targetwhile (current->next[i] != nullptr && current->next[i]->value < target) {current = current->next[i];}}// 现在current是底层中小于target的最大节点current = current->next[0];return current != nullptr && current->value == target;
}

查找过程示例(查找11):

text

第2层:1 ---------------> 9 (11>9,跳到9)↓
第1层:9 ------> 13 (11<13,下降)↓  
第0层:9 -> 11 (找到!)

5.2 插入操作

cpp

void insert(int value) {vector<SkipListNode*> update(maxLevel, nullptr);SkipListNode* current = head;// 1. 找到每层中待插入位置的前驱节点for (int i = currentLevel - 1; i >= 0; i--) {while (current->next[i] != nullptr && current->next[i]->value < value) {current = current->next[i];}update[i] = current;  // 记录每层的前驱节点}// 2. 随机决定新节点的层数int newLevel = randomLevel();if (newLevel > currentLevel) {for (int i = currentLevel; i < newLevel; i++) {update[i] = head;}currentLevel = newLevel;}// 3. 创建新节点并插入到各层SkipListNode* newNode = new SkipListNode(value, newLevel);for (int i = 0; i < newLevel; i++) {newNode->next[i] = update[i]->next[i];update[i]->next[i] = newNode;}
}

插入过程图示(插入8,随机到2层):

text

插入前:
第1层:1 ------> 5 ------> 9|        |        |
第0层:1 -> 3 -> 5 -> 7 -> 9插入后:
第1层:1 ------> 5 -> 8 -> 9|        |    |    |
第0层:1 -> 3 -> 5 -> 7 -> 8 -> 9

5.3 删除操作

cpp

bool remove(int value) {vector<SkipListNode*> update(maxLevel, nullptr);SkipListNode* current = head;// 1. 找到包含该节点的各层前驱for (int i = currentLevel - 1; i >= 0; i--) {while (current->next[i] != nullptr && current->next[i]->value < value) {current = current->next[i];}update[i] = current;}// 2. 检查是否存在该节点current = current->next[0];if (current == nullptr || current->value != value) {return false;}// 3. 从各层中删除该节点for (int i = 0; i < currentLevel; i++) {if (update[i]->next[i] != current) {break;}update[i]->next[i] = current->next[i];}delete current;// 4. 更新当前层数(如果最高层变空)while (currentLevel > 1 && head->next[currentLevel - 1] == nullptr) {currentLevel--;}return true;
}

6. 时间复杂度分析

6.1 查找时间复杂度

  • 最坏情况:O(n) - 所有节点都在同一层

  • 平均情况:O(log n) - 通过多级索引加速

数学推导

  • 第0层:n个节点

  • 第1层:约n/2个节点

  • 第2层:约n/4个节点

  • ...

  • 第k层:约n/2^k个节点

  • 当n/2^k = 1时,k = log₂n

6.2 空间复杂度

  • 额外空间:O(n) - 索引节点总数约为 n + n/2 + n/4 + ... ≈ 2n

7. 跳表 vs 平衡树

特性跳表平衡树(如红黑树)
实现难度简单复杂
查找性能O(log n)O(log n)
插入性能O(log n)O(log n)
删除性能O(log n)O(log n)
范围查询容易相对复杂
内存使用较多较少
并发控制相对容易复杂

8. 实际应用场景

8.1 Redis中的有序集合

cpp

// Redis使用跳表实现ZSET
typedef struct zset {dict *dict;          // 哈希表,用于O(1)查找zskiplist *zsl;      // 跳表,用于范围查询和排序
} zset;

8.2 LevelDB/RocksDB

用于内存中的MemTable实现,支持快速的范围查询。

8.3 Lucene

在倒排索引中使用跳表进行文档ID的存储和查询。

9. 完整代码示例

cpp

#include <iostream>
#include <vector>
#include <climits>
#include <cstdlib>
#include <ctime>using namespace std;struct SkipListNode {int value;vector<SkipListNode*> next;SkipListNode(int val, int level) : value(val), next(level, nullptr) {}
};class SkipList {
private:SkipListNode* head;int maxLevel;int currentLevel;float probability;public:SkipList(int maxLvl = 16, float p = 0.5) : maxLevel(maxLvl), probability(p), currentLevel(1) {srand(time(0));head = new SkipListNode(INT_MIN, maxLevel);}int randomLevel() {int level = 1;while ((rand() / (float)RAND_MAX) < probability && level < maxLevel) {level++;}return level;}void insert(int value) {vector<SkipListNode*> update(maxLevel, nullptr);SkipListNode* current = head;for (int i = currentLevel - 1; i >= 0; i--) {while (current->next[i] != nullptr && current->next[i]->value < value) {current = current->next[i];}update[i] = current;}int newLevel = randomLevel();if (newLevel > currentLevel) {for (int i = currentLevel; i < newLevel; i++) {update[i] = head;}currentLevel = newLevel;}SkipListNode* newNode = new SkipListNode(value, newLevel);for (int i = 0; i < newLevel; i++) {newNode->next[i] = update[i]->next[i];update[i]->next[i] = newNode;}cout << "插入 " << value << ",层数: " << newLevel << endl;}bool search(int target) {SkipListNode* current = head;for (int i = currentLevel - 1; i >= 0; i--) {while (current->next[i] != nullptr && current->next[i]->value < target) {current = current->next[i];}}current = current->next[0];bool found = (current != nullptr && current->value == target);cout << "查找 " << target << ": " << (found ? "找到" : "未找到") << endl;return found;}void display() {cout << "\n跳表结构:" << endl;for (int i = currentLevel - 1; i >= 0; i--) {SkipListNode* node = head->next[i];cout << "第" << i << "层: ";while (node != nullptr) {cout << node->value << " -> ";node = node->next[i];}cout << "NULL" << endl;}}
};// 测试示例
int main() {SkipList skiplist;skiplist.insert(3);skiplist.insert(6);skiplist.insert(7);skiplist.insert(9);skiplist.insert(12);skiplist.insert(19);skiplist.insert(17);skiplist.display();skiplist.search(6);skiplist.search(15);return 0;
}

10. 总结

跳表的核心优势:

  1. 实现简单:比平衡树容易理解和实现

  2. 性能优秀:平均O(log n)的查找、插入、删除

  3. 支持范围查询:天然支持有序遍历

  4. 并发友好:更容易实现线程安全版本

跳表通过"空间换时间"和"概率平衡"的思想,在保持链表简单性的同时,获得了接近平衡树的性能,是现代系统中广泛使用的重要数据结构。

http://www.dtcms.com/a/586824.html

相关文章:

  • 沧县网站建设公司html页面制作
  • 多用户商城网站企业邮箱什么格式
  • 徐州专业网站建设公司房地产网站建设解决方案
  • 佛山企业模板建站广州地铁
  • Xshell效率实战:SSH管理秘籍(三)
  • C++中的inline函数(内联函数)
  • 局域网内建网站电子商务网站建设asp sql 源码下载
  • 个人信息网站模板网站开发中如何实现gps定位
  • netcore 托管Redis服务
  • asp网站建设报告书手机网站欣赏
  • 网站建设功能描述书网站建设电话销售的话术
  • 测试用例的设计思路及方法
  • cms建站系统 开源劳务输送网站建设方案
  • 自建网站教程海宁市住房和城乡规划建设局网站
  • 静态网站更新c .net网站开发视频教程
  • 绍兴网站制作方案定制借贷网站开发是否合法
  • 上海优质网站seo有哪些seo优化推广工程师
  • 洛阳霞光建设网站网站公司怎么找客户
  • 公司网站能自己做二维码phpcms v9 网站建设设计制作网络科技模板
  • 每天一篇好文章网站专业网站建设哪个好
  • 赣州网站建设培训毕业设计做课程网站好
  • 8.基础--SQL--DDL-表操作-修改删除
  • Java 大视界 --Java 大数据在智能医疗远程手术机器人控制与数据传输中的技术支持
  • 格瑞特网站建设只能用域名访问WordPress
  • 巩义网站建设河北网站制作
  • 淘宝建设网站的理由上海小程序搭建
  • Java枚举类型详解:让你的程序更具表达力和可读性!
  • 网站开发公司怎么查电子商务网站设计分析怎么做
  • 住宅代理能用多久?其寿命影响因素与延长技巧
  • 网站后台数据库丢失郑州东区网站建设