跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。在大部分情况下,跳跃表的效率可以和平衡树相媲美,而且实现比平衡树更加简单。
Redis 使用跳跃表作为有序集合的底层实现之一,如果一个有序集合包含的元素较多,或者有序集合中的元素是较长的字符串时,Redis 就会使用跳跃表来维护数据。
接下来一起看下跳跃表的原理和基于C++的实现代码。
跳跃表本质上是个有序的链表,其通过在节点上随机的添加辅助连接使得查找的时间复杂度从O(N)变为平均O(logN),最坏O(N)。
如上图所示,每个节点都有添加了辅助连接。我们可以通过辅助节点来加快搜索过程:在顶层的链表进行扫描,直到 遇到一个含有较小关键字且指向一个含有较大关键字节点的节点,或者到达这一层的最后一个节点,然后下降到下一层辅助节点继续查找,直到确认目标值不存在或者找到了目标值所在节点。举个例子,在上图中跳跃表寻找 58 的过程如下:
Q:查找过程中为何没有用到数据节点自身的next指针呢?
A: 因为第一层的辅助节点等价于数据节点自身的next。其实在跳跃表中,数据节点只有数据,辅助节点中只有next指针。
Q: 当数据节点变多时,三层辅助节点好像不太够用?
A: 当跳跃表的数据节点变多时,我们可以加入更多层的辅助节点来保证足够快的查找速度。
为了初始化跳跃表,我们需要一个头结点,它含有M层辅助节点,每个辅助节点都指向NULL。M是整个跳跃表中辅助节点的层数上限。SkipList 类的定义如下,我们在构造函数中对头结点进行初始化。
// SkipList 的定义
template<typename DataType>
class SkipList {
enum {
MAX_LEVEL = 32, // 允许的最大层数
};
struct Node {
std::vector<Node*> next; //存储辅助信息的 next
DataType data; // 数据
};
private:
uint8_t max_level; //真正指定的最大层数
Node head_node; //头结点
int randomLevelNumber() const; //以概率 1/2^j 返回 j, j ∈ [1, max_level];
public:
SkipList(uint8_t ml = MAX_LEVEL) : max_level(ml) {
if(max_level > MAX_LEVEL || max_level <= 2) {
max_level = MAX_LEVEL;
}
head_node.next.resize(max_level); // 初始化头结点的 next 数组,全部指向 nullptr;
}
bool isExist(const DataType &) const; // 判断目标元素是否存在
bool erase(const DataType &); // 删除元素
bool insert(const DataType &); // 插入元素
typedef function<bool(const DataType&)> HandlerType;
void walk(HandlerType &) const; // 暴露一个遍历接口
};
当我们向跳跃表插入一个新节点时,需要解决的第一个问题就是新增节点要有多少层next指针。如果每 t 个节点中就有一个节点至少具备两层 next 指针,则我们在第二层可以一次跳跃 t 个节点,以此类推,每 t^j 个节点中,就有一个节点至少具备j+1层next指针。
为使节点具有上述性质,我们需要一个以概率 1/t^j 返回 j+1 的 随机函数。
// 随机函数的定义
// FIXME
// 这里有点问题,i 为 max_level 和 max_level-1 的概率是相同的,
template<typename DataType>
int SkipList<DataType>::randomLevelNumber() const {
int i, j, t = rand();
for(i = 1, j = 2; i < max_level; i++, j *= 2) {
if(t > RAND_MAX/j) {
break;
}
}
return i;
}
// 插入函数
template<typename DataType>
bool SkipList<DataType>::insert(const DataType &data) {
if(isExist(data)) {
return false;
}
int level = randomLevelNumber();
Node *new_node = new Node();
new_node->data = data;
new_node->next.resize(level);
int cur_level = max_level - 1; // 因为 level 是从 0 开始的。
Node *cur_node = &head_node;
while(cur_level >= 0) {
while(cur_node->next[cur_level] != nullptr
&& cur_node->next[cur_level]->data < data) {
cur_node = cur_node->next[cur_level];
}
if(new_node->next.size() > cur_level) {
new_node->next[cur_level] = cur_node->next[cur_level];
cur_node->next[cur_level] = new_node;
}
-- cur_level;
}
return true;
}
调用了 1666112 次,得到的层数基本还是符合预期的,具体的分布如下:
层数 | 数量 | 比率 |
---|---|---|
23 | 1 | 6.002e-07 |
22 | 1 | 6.002e-07 |
20 | 1 | 6.002e-07 |
19 | 3 | 1.8006e-06 |
18 | 7 | 4.2014e-06 |
17 | 15 | 9.003e-06 |
16 | 38 | 2.28076e-05 |
15 | 66 | 3.96132e-05 |
14 | 105 | 6.3021e-05 |
13 | 214 | 0.000128443 |
12 | 367 | 0.000220273 |
11 | 813 | 0.000487962 |
10 | 1598 | 0.000959119 |
9 | 3265 | 0.00195965 |
8 | 6385 | 0.00383228 |
7 | 13078 | 0.00784941 |
6 | 25849 | 0.0155146 |
5 | 52201 | 0.031331 |
4 | 103711 | 0.0622473 |
3 | 207830 | 0.12474 |
2 | 417042 | 0.250309 |
1 | 833522 | 0.50028 |
这个随机函数保证:
插入的步骤和搜索的套路类似,只是需要在插入过程中更新对应层的 next 指针,具体的流程如下:
删除和插入过程类似,只是从建立链接变成了删除链接,寻找目标节点的流程是一样的,就不再赘述了。
template<typename DataType>
bool SkipList<DataType>::erase(const DataType &data) {
int cur_level = max_level - 1;
Node *cur_node = &head_node;
while(cur_level >= 0) {
while(cur_node->next[cur_level] != nullptr
&& cur_node->next[cur_level]->data < data) {
cur_node = cur_node->next[cur_level];
}
if(cur_node->next[cur_level] != nullptr
&& !(data < cur_node->next[cur_level]->data)) {
auto remove_node = cur_node->next[cur_level];
cur_node->next[cur_level] = cur_node->next[cur_level]->next[cur_level];
remove_node->next[cur_level] = nullptr;
if(cur_level == 0) {
delete(remove_node);
}
}
--cur_level;
}
return 0;
}
#include
#include
#include
#include
using namespace std;
template<typename DataType>
class SkipList {
enum {
MAX_LEVEL = 32, // 允许的最大层数
};
struct Node {
std::vector<Node*> next; //存储辅助信息的 next
DataType data; // 数据
};
private:
uint8_t max_level; //真正指定的最大层数
Node head_node; //头结点
int randomLevelNumber() const; //以概率 1/2^j 返回 j, j ∈ [1, max_level];
public:
SkipList(uint8_t ml = MAX_LEVEL) : max_level(ml) {
if(max_level > MAX_LEVEL || max_level <= 2) {
max_level = MAX_LEVEL;
}
head_node.next.resize(max_level); // 初始化头结点的 next 数组,全部指向 nullptr;
}
bool isExist(const DataType &) const; // 判断目标元素是否存在
bool erase(const DataType &); // 删除元素
bool insert(const DataType &); // 插入元素
typedef function<bool(const DataType&)> HandlerType;
void walk(HandlerType &) const; // 暴露一个遍历接口
};
template<typename DataType>
void SkipList<DataType>::walk(HandlerType &handler) const {
auto cur_node = &head_node;
while(cur_node->next[0] != nullptr) {
cur_node = cur_node->next[0];
if(!handler(cur_node->data)) {
break;
}
}
}
// FIXME
// 这里有点问题,i 为 max_level 和 max_level-1 的概率是相同的,
template<typename DataType>
int SkipList<DataType>::randomLevelNumber() const {
int i, j, t = rand();
for(i = 1, j = 2; i < max_level; i++, j *= 2) {
if(t > RAND_MAX/j) {
break;
}
}
return i;
}
template<typename DataType>
bool SkipList<DataType>::isExist(const DataType &data) const {
int cur_level = max_level - 1;
const Node *cur_node = &head_node;
while(cur_level >= 0) {
while(cur_node->next[cur_level] != nullptr
&& cur_node->next[cur_level]->data < data) {
cur_node = cur_node->next[cur_level];
}
if(cur_node->next[cur_level] != nullptr
&& !(data < cur_node->next[cur_level]->data)) {
return true;
}
--cur_level;
}
return false;
}
template<typename DataType>
bool SkipList<DataType>::erase(const DataType &data) {
int cur_level = max_level - 1;
Node *cur_node = &head_node;
while(cur_level >= 0) {
while(cur_node->next[cur_level] != nullptr
&& cur_node->next[cur_level]->data < data) {
cur_node = cur_node->next[cur_level];
}
if(cur_node->next[cur_level] != nullptr
&& !(data < cur_node->next[cur_level]->data)) {
auto remove_node = cur_node->next[cur_level];
cur_node->next[cur_level] = cur_node->next[cur_level]->next[cur_level];
remove_node->next[cur_level] = nullptr;
if(cur_level == 0) {
delete(remove_node);
}
}
--cur_level;
}
return 0;
}
template<typename DataType>
bool SkipList<DataType>::insert(const DataType &data) {
if(isExist(data)) {
return false;
}
int level = randomLevelNumber();
Node *new_node = new Node();
new_node->data = data;
new_node->next.resize(level);
int cur_level = max_level - 1; // 因为 level 是从 0 开始的。
Node *cur_node = &head_node;
while(cur_level >= 0) {
while(cur_node->next[cur_level] != nullptr
&& cur_node->next[cur_level]->data < data) {
cur_node = cur_node->next[cur_level];
}
if(new_node->next.size() > cur_level) {
new_node->next[cur_level] = cur_node->next[cur_level];
cur_node->next[cur_level] = new_node;
}
-- cur_level;
}
return true;
}