原理
跳表(Skip List) 是一种随机化数据结构 ,用于高效查找、插入和删除 ,尤其适用于有序数据集合。相比链表,跳表通过多层索引结构加速查找,期望时间复杂度接近 O(logn)。跳表的主要思想是:
- 底层链表存储所有数据元素,保持有序。
- 上层链表是稀疏索引,用于跳过部分节点,减少遍历的长度。
结构图
下面是一个跳表的结构示意:
bash
Level 3: [1] ----------------> [9]
Level 2: [1] -----> [4] -----> [9]
Level 1: [1] -----> [4] -----> [7] -----> [9]
Level 0: [1] -> [2] -> [4] -> [5] -> [7] -> [8] -> [9]
如上图所示:
- 每一层都是一个有序链表。
- 每一层的节点是下一层的子集,存储重要的中间节点,形成分层索引。
- 查找过程:从最高层开始,先向右移动,如果目标值超出范围,则向下移动到下一层。重复此过程直到找到目标节点。
优缺点
优点:
- 插入、删除和查找的期望时间复杂度为 O(logn)。
- 实现简单,比红黑树和AVL树更容易理解和维护。
缺点:
- 需要额外的空间存储索引层节点。
代码示例(c++)
下面是一段简单的 C++ 跳表实现,包括节点结构定义、插入、查找和显示功能。
c++
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>
using namespace std;
class Node {
public:
int value;
vector<Node*> forward; // 每层的前向指针
Node(int val, int level) : value(val), forward(level + 1, nullptr) {}
};
class SkipList {
private:
int maxLevel; // 跳表的最大层数
float probability; // 晋升概率
Node* header; // 头节点
int currentLevel; // 当前跳表的层数
public:
SkipList(int maxLevel, float probability)
: maxLevel(maxLevel), probability(probability), currentLevel(0) {
header = new Node(-1, maxLevel); // 头节点初始化为-1
srand(time(nullptr)); // 初始化随机数种子
}
// 随机生成节点的层数
int randomLevel() {
int lvl = 0;
while ((rand() / double(RAND_MAX)) < probability && lvl < maxLevel) {
lvl++;
}
return lvl;
}
// 插入新节点
void insert(int value) {
vector<Node*> update(maxLevel + 1);
Node* current = header;
// 从最高层向下查找插入位置
for (int i = currentLevel; i >= 0; i--) {
while (current->forward[i] && current->forward[i]->value < value) {
current = current->forward[i];
}
update[i] = current;
}
// 在底层插入节点的位置
current = current->forward[0];
// 如果节点不存在,则插入新节点
if (!current || current->value != value) {
int lvl = randomLevel();
if (lvl > currentLevel) {
for (int i = currentLevel + 1; i <= lvl; i++) {
update[i] = header;
}
currentLevel = lvl;
}
Node* newNode = new Node(value, lvl);
for (int i = 0; i <= lvl; i++) {
newNode->forward[i] = update[i]->forward[i];
update[i]->forward[i] = newNode;
}
cout << "Inserted value: " << value << " at level: " << lvl << endl;
}
}
// 查找节点
bool search(int value) {
Node* current = header;
for (int i = currentLevel; i >= 0; i--) {
while (current->forward[i] && current->forward[i]->value < value) {
current = current->forward[i];
}
}
current = current->forward[0];
return current && current->value == value;
}
// 打印跳表结构
void display() {
for (int i = currentLevel; i >= 0; i--) {
Node* current = header->forward[i];
cout << "Level " << i << ": ";
while (current) {
cout << current->value << " ";
current = current->forward[i];
}
cout << endl;
}
}
};
int main() {
SkipList skipList(4, 0.5); // 最大层数为4,晋升概率为0.5
skipList.insert(3);
skipList.insert(6);
skipList.insert(7);
skipList.insert(9);
skipList.insert(12);
skipList.insert(19);
cout << "Skip List Structure:" << endl;
skipList.display();
cout << "Search 7: " << (skipList.search(7) ? "Found" : "Not Found") << endl;
cout << "Search 4: " << (skipList.search(4) ? "Found" : "Not Found") << endl;
return 0;
}
代码解析
- Node 类 :
- 表示跳表中的节点,包含节点的值和指向不同层节点的指针向量
forward
。
- 表示跳表中的节点,包含节点的值和指向不同层节点的指针向量
- SkipList 类 :
- 实现了跳表的主要功能,包括插入、查找和显示结构。
randomLevel
:用于随机生成节点的层数,控制索引的稀疏程度。insert
:插入元素到跳表中,如果新节点的层数超过当前最大层数,则更新索引。search
:查找目标值是否存在。
- 主函数 :
- 初始化跳表并插入若干元素,测试插入和查找功能。
运行结果
bash
Inserted value: 3 at level: 0
Inserted value: 6 at level: 1
Inserted value: 7 at level: 2
Inserted value: 9 at level: 0
Inserted value: 12 at level: 1
Inserted value: 19 at level: 3
Skip List Structure:
Level 3: 19
Level 2: 7 19
Level 1: 6 12 19
Level 0: 3 6 7 9 12 19
Search 7: Found
Search 4: Not Found
总结
- 时间复杂度:查找、插入、删除的期望时间复杂度为 O(logn)O(\log n)O(logn)。
- 空间复杂度:O(nlogn)O(n \log n)O(nlogn),因为每个节点可能会出现在多层中。
跳表的实现简单且高效,常用于 Redis 等数据库的有序集合。