Skiplist
起因, Leetcode 1206. 设计跳表, 愣是没通过题目给的动图看懂搜索过程, 遂学之
基本思想
看图! 同一个结点之间, 从高层至低层有连接.
查找, 插入, 删除的时间复杂度都是 $O(\log{n})$, 时间期望与红黑树一样, 不过跳表是有序链表的改进, 而非二叉树.
有序链表的查找时间复杂度显然 $O(n)$, 但跳表引入了分层. 最底层是初始有序链表, 定义位于第 $i$ 层的结点出现在第 $i+1$ 层的概率为 $p$(常数).
查找时, 从高到低层查找. 在每层逐个比较直至满足当前结点的下一结点大于等于目标值时, 进入下一层, 直至到第一层结束. 最终, 若下一结点值为目标值则查询成功, 反之元素不存在. 由于从高往低搜索, 所以会跳过一些无意义的比较, 最终能达到 $O(\log{n})$
获取结点最大层数
实现方法很简单, 随机数模拟概率, 成功则最大层数增加一层
1
2
3
4
5
6
7
8
9
10
mt19937 gen{random_device()};
// 默认输出[0, 1)均匀分布
uniform_real_distribution<double> dis;
int randomLevel()
{
int cur = 1;
while (dis(gen) < p && cur < maxlevel)
++cur;
return cur;
}
查询
从高到低, 每一层向右搜寻, 直至当前层下一个结点值大于等于目标值. 最终的结点与下一结点的值满足: $cur.val < target \leq cur.next.val$
1
2
3
4
5
6
7
8
9
10
11
bool search(int target)
{
auto cur = root;
for (int level = maxlevel - 1; level >= 0; --level)
while (cur->next[level] && cur->next[level]->val < target)
cur = cur->next[level];
cur = cur->next[0];
if (cur && cur->val == target)
return true;
return false;
}
插入
由于需要知道每层要插入的地点, 因此其中包含查询过程. 每层都记录下待插入的结点, 最终通过随机数获取当前结点的最大层数 curLevel
, 从存储的 $[0, curLevel]$ 结点后插入新结点.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void add(int target)
{
vector<node *> update(maxlevel);
auto cur = root;
for (int level = maxlevel - 1; level >= 0; --level)
{
while (cur->next[level] && cur->next[level]->val < target)
cur = cur->next[level];
update[level] = cur;
}
int curLevel = randomLevel();
node *insert = new node(target, maxlevel);
for (int level = 0; level <= curLevel; ++level)
{
// update -> insert -> update.next
insert->next[level] = update[level]->next[level];
update[level]->next[level] = insert;
}
}
删除
同理插入, 记录待删除结点的前一结点位置. 根据下一结点信息可以进行剪枝(若下一结点值非目标值, 则证明已经删完与目标值对应的结点, 可以提前退出循环)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool erase(int target)
{
vector<node *> update(maxlevel);
auto cur = root;
for (int level = maxlevel - 1; level >= 0; --level)
{
while (cur->next[level] && cur->next[level]->val < target)
cur = cur->next[level];
update[level] = cur;
}
cur = cur->next[0];
if (!cur || cur->val != target)
return false;
for (int level = 0; level < maxlevel; ++level)
{
if (update[level]->next[level] != cur)
break;
update[level]->next[level] = cur->next[level];
}
delete cur;
return true;
}
总结
这玩意儿, 第一次见到半小时内肯定写不出的阿. 光是思想就算不上简单了, 而且还是基于链表的, 指针操作等等细节也需要很注意. 写起来很麻烦. 下述代码可以通过前文提及的 Leetcode 题目.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class Skiplist
{
private:
struct node
{
int val;
vector<node *> next;
node(const int V = -1, const int MAXLEVEL = 32) : val(V), next(MAXLEVEL, nullptr) {}
};
node *root;
int maxlevel;
double p;
mt19937 gen{random_device{}()};
uniform_real_distribution<double> dis;
int randomLevel()
{
int cur = 0;
while (dis(gen) < p && cur + 1 < maxlevel)
++cur;
return cur;
}
public:
Skiplist(const int MAXLEVEL = 32, const double P = 0.25) : maxlevel(MAXLEVEL), p(P), root(new node()) {}
bool search(int target)
{
auto cur = root;
for (int level = maxlevel - 1; level >= 0; --level)
while (cur->next[level] && cur->next[level]->val < target)
cur = cur->next[level];
cur = cur->next[0];
if (cur && cur->val == target)
return true;
return false;
}
void add(int target)
{
vector<node *> update(maxlevel);
auto cur = root;
for (int level = maxlevel - 1; level >= 0; --level)
{
while (cur->next[level] && cur->next[level]->val < target)
cur = cur->next[level];
update[level] = cur;
}
int curLevel = randomLevel();
node *insert = new node(target, maxlevel);
for (int level = 0; level <= curLevel; ++level)
{
// update -> insert -> update.next
insert->next[level] = update[level]->next[level];
update[level]->next[level] = insert;
}
}
bool erase(int target)
{
vector<node *> update(maxlevel);
auto cur = root;
for (int level = maxlevel - 1; level >= 0; --level)
{
while (cur->next[level] && cur->next[level]->val < target)
cur = cur->next[level];
update[level] = cur;
}
cur = cur->next[0];
if (!cur || cur->val != target)
return false;
for (int level = 0; level < maxlevel; ++level)
{
if (update[level]->next[level] != cur)
break;
update[level]->next[level] = cur->next[level];
}
delete cur;
return true;
}
~Skiplist() { delete root; }
};
This post is licensed under CC BY 4.0 by the author.