跳表
跳表 (Skip List) 是由 William Pugh 发明的一种查找数据结构,支持对数据的快速查找,插入和删除。
跳表的期望空间复杂度为
基本思想
顾名思义,跳表是一种类似于链表的数据结构。更加准确地说,跳表是对有序链表的改进。
为方便讨论,后续所有有序链表默认为 升序 排序。
一个有序链表的查找操作,就是从头部开始逐个比较,直到当前节点的值大于或者等于目标节点的值。很明显,这个操作的复杂度是
跳表在有序链表的基础上,引入了 分层 的概念。首先,跳表的每一层都是一个有序链表,特别地,最底层是初始的有序链表。每个位于第
记在 n 个节点的跳表中,期望包含
在跳表中查找,就是从第
复杂度证明
空间复杂度
对于一个节点而言,节点的最高层数为
在最坏的情况下,每一层有序链表等于初始有序链表,即跳表的 最差空间复杂度 为
时间复杂度
从后向前分析查找路径,这个过程可以分为从最底层爬到第
假设当前我们处于一个第
令
解得
由此可以得出:在长度为
现在只需要分析爬到第
所以,跳表查询的期望查找步数为
在最坏的情况下,每一层有序链表等于初始有序链表,查找过程相当于对最高层的有序链表进行查询,即跳表查询操作的 最差时间复杂度 为
插入操作和删除操作就是进行一遍查询的过程,途中记录需要修改的节点,最后完成修改。易得每一层至多只需要修改一个节点,又因为跳表期望层数为
具体实现
获取节点的最大层数
模拟以
int randomLevel() {
int lv = 1;
// MAXL = 32, S = 0xFFFF, PS = S * P, P = 1 / 4
while ((rand() & S) < PS) ++lv;
return min(MAXL, lv);
}
查询
查询跳表中是否存在键值为 key
的节点。具体实现时,可以设置两个哨兵节点以减少边界条件的讨论。
V& find(const K& key) {
SkipListNode<K, V>* p = head;
// 找到该层最后一个键值小于 key 的节点,然后走向下一层
for (int i = level; i >= 0; --i) {
while (p->forward[i]->key < key) {
p = p->forward[i];
}
}
// 现在是小于,所以还需要再往后走一步
p = p->forward[0];
// 成功找到节点
if (p->key == key) return p->value;
// 节点不存在,返回 INVALID
return tail->value;
}
插入
插入节点 (key, value)
。插入节点的过程就是先执行一遍查询的过程,中途记录新节点是要插入哪一些节点的后面,最后再执行插入。每一层最后一个键值小于 key
的节点,就是需要进行修改的节点。
void insert(const K &key, const V &value) {
// 用于记录需要修改的节点
SkipListNode<K, V> *update[MAXL + 1];
SkipListNode<K, V> *p = head;
for (int i = level; i >= 0; --i) {
while (p->forward[i]->key < key) {
p = p->forward[i];
}
// 第 i 层需要修改的节点为 p
update[i] = p;
}
p = p->forward[0];
// 若已存在则修改
if (p->key == key) {
p->value = value;
return;
}
// 获取新节点的最大层数
int lv = randomLevel();
if (lv > level) {
lv = ++level;
update[lv] = head;
}
// 新建节点
SkipListNode<K, V> *newNode = new SkipListNode<K, V>(key, value, lv);
// 在第 0~lv 层插入新节点
for (int i = lv; i >= 0; --i) {
p = update[i];
newNode->forward[i] = p->forward[i];
p->forward[i] = newNode;
}
++length;
}
删除
删除键值为 key
的节点。删除节点的过程就是先执行一遍查询的过程,中途记录要删的节点是在哪一些节点的后面,最后再执行删除。每一层最后一个键值小于 key
的节点,就是需要进行修改的节点。
bool erase(const K &key) {
// 用于记录需要修改的节点
SkipListNode<K, V> *update[MAXL + 1];
SkipListNode<K, V> *p = head;
for (int i = level; i >= 0; --i) {
while (p->forward[i]->key < key) {
p = p->forward[i];
}
// 第 i 层需要修改的节点为 p
update[i] = p;
}
p = p->forward[0];
// 节点不存在
if (p->key != key) return false;
// 从最底层开始删除
for (int i = 0; i <= level; ++i) {
// 如果这层没有 p 删除就完成了
if (update[i]->forward[i] != p) {
break;
}
// 断开 p 的连接
update[i]->forward[i] = p->forward[i];
}
// 回收空间
delete p;
// 删除节点可能导致最大层数减少
while (level > 0 && head->forward[level] == tail) --level;
// 跳表长度
--length;
return true;
}
完整代码
下列代码是用跳表实现的 map。未经正经测试,仅供参考。
#include <bits/stdc++.h>
using namespace std;
template <typename K, typename V>
struct SkipListNode {
int level;
K key;
V value;
SkipListNode **forward;
SkipListNode() {}
SkipListNode(K k, V v, int l, SkipListNode *nxt = NULL) {
key = k;
value = v;
level = l;
forward = new SkipListNode *[l + 1];
for (int i = 0; i <= l; ++i) forward[i] = nxt;
}
~SkipListNode() {
if (forward != NULL) delete[] forward;
}
};
template <typename K, typename V>
struct SkipList {
static const int MAXL = 32;
static const int P = 4;
static const int S = 0xFFFF;
static const int PS = S / P;
static const int INVALID = INT_MAX;
SkipListNode<K, V> *head, *tail;
int length;
int level;
SkipList() {
srand(time(0));
level = length = 0;
tail = new SkipListNode<K, V>(INVALID, 0, 0);
head = new SkipListNode<K, V>(INVALID, 0, MAXL, tail);
}
~SkipList() {
delete head;
delete tail;
}
int randomLevel() {
int lv = 1;
while ((rand() & S) < PS) ++lv;
return MAXL > lv ? lv : MAXL;
}
void insert(const K &key, const V &value) {
SkipListNode<K, V> *update[MAXL + 1];
SkipListNode<K, V> *p = head;
for (int i = level; i >= 0; --i) {
while (p->forward[i]->key < key) {
p = p->forward[i];
}
update[i] = p;
}
p = p->forward[0];
if (p->key == key) {
p->value = value;
return;
}
int lv = randomLevel();
if (lv > level) {
lv = ++level;
update[lv] = head;
}
SkipListNode<K, V> *newNode = new SkipListNode<K, V>(key, value, lv);
for (int i = lv; i >= 0; --i) {
p = update[i];
newNode->forward[i] = p->forward[i];
p->forward[i] = newNode;
}
++length;
}
bool erase(const K &key) {
SkipListNode<K, V> *update[MAXL + 1];
SkipListNode<K, V> *p = head;
for (int i = level; i >= 0; --i) {
while (p->forward[i]->key < key) {
p = p->forward[i];
}
update[i] = p;
}
p = p->forward[0];
if (p->key != key) return false;
for (int i = 0; i <= level; ++i) {
if (update[i]->forward[i] != p) {
break;
}
update[i]->forward[i] = p->forward[i];
}
delete p;
while (level > 0 && head->forward[level] == tail) --level;
--length;
return true;
}
V &operator[](const K &key) {
V v = find(key);
if (v == tail->value) insert(key, 0);
return find(key);
}
V &find(const K &key) {
SkipListNode<K, V> *p = head;
for (int i = level; i >= 0; --i) {
while (p->forward[i]->key < key) {
p = p->forward[i];
}
}
p = p->forward[0];
if (p->key == key) return p->value;
return tail->value;
}
bool count(const K &key) { return find(key) != tail->value; }
};
int main() {
SkipList<int, int> L;
map<int, int> M;
clock_t s = clock();
for (int i = 0; i < 1e5; ++i) {
int key = rand(), value = rand();
L[key] = value;
M[key] = value;
}
for (int i = 0; i < 1e5; ++i) {
int key = rand();
if (i & 1) {
L.erase(key);
M.erase(key);
} else {
int r1 = L.count(key) ? L[key] : 0;
int r2 = M.count(key) ? M[key] : 0;
assert(r1 == r2);
}
}
clock_t e = clock();
cout << "Time elapsed: " << (double)(e - s) / CLOCKS_PER_SEC << endl;
// about 0.2s
return 0;
}
跳表的随机访问优化
访问跳表中第
跳表的随机访问优化就是对每一个前向指针,再多维护这个前向指针的长度。假设
现在访问跳表中的第
这样,就可以快速地访问到跳表的第
参考资料
贡献者:@Cookie@ksyx@mgt@Zhikai@Shuhao@Xeonacid@Backl1ght
本页面最近更新:2/3/2023, 12:00:00 AM,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用