本文最后更新于:2021年7月20日 下午
跳表(Skiplist)是一个特殊的链表,相比一般的链表,有更高的查找效率,可比拟二叉查找树,平均期望的查找、插入、删除时间复杂度都是O(logn),许多知名的开源软件(库)中的数据结构均采用了跳表这种数据结构。
- Redis中的有序集合zset
- LevelDB、RocksDB、HBase中Memtable
- ApacheLucene中的TermDictionary、Posting List
跳跃链表的最大优势在于无论是查找、插入和删除都是O(logn),不过由于跳跃链表的操作是基于概率形成的,那么它操作复杂度大于O(logn)的概率为,可以看出当n越大的时候失败的概率越小。
跳跃链表的空间复杂度的期望为O(n),链表的层数期望为O(logn).
跳表的结构
跳跃表由多条链构成(L0,L1,L2 ……,Lh),且满足如下三个条件:
- 每条链必须包含两个特殊元素:+∞ 和 -∞(其实不需要)
- L0包含所有的元素,并且所有链中的元素按照升序排列。
- 每条链中的元素集合必须包含于序数较小的链的元素集合。
性质:
一种插入、查询、删除时间效率为O(logn),空间效率为O(n) 的数据结构。
数据结构:
| struct node { int key; struct node *forward[MAXlevel]; };
|
高度控制策略
跳表高度控制的策略,有如下两种:
- 限制最大高度:插入算法中限制跳表的最大高度,
h=max{10, 3* RoundUp(logn)}
,当超过最大高度的时候,即使随机函数值为1也不能继续在 Tower上Append Entry了。边界情况,当RoundUP(logn) <RoundUp(log(n+1)),仍然可以append一次。
- 不限制最大高度:插入算法中不限制跳表的最大高度,以随机函数的值来确定是否继续在Tower上Append Entry。(后面会给出高度>log(n)的概率推导,概率是非常低的,所以这种方法也能Work)
查找:
在跳跃表中查找一个元素x,按照如下几个步骤进行:
从最上层的链(Lh)的开头开始
假设当前位置为p,它向右指向的节点为q(p与q不一定相邻),且q的值为y。将y与x作比较
(1) x=y 输出查询成功及相关信息
(2) x>y 从p向右移动到q的位置
(3) x<y 从p向下移动一格
如果当前位置在最底层的链中(L0),且还要往下移动的话,则输出查询失败

插入:
向跳跃表中插入一个元素,相当于在表中插入一列从S0中某一位置出发向上的连续一段元素。有两个参数需要确定,即插入列的位置以及它的“高度”。

删除:
删除操作分为以下三个步骤:
- 在跳跃表中查找到这个元素的位置,如果未找到,则退出
- 将该元素所在整列从表中删除
- 将多余的“空链”删除
实现:
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
| #include <random> #include <iostream> using namespace std; #define MAXLEVEL 4
int randomLevel() { int k = 1; while (rand() % 2) { k++; } k = (k < MAXLEVEL) ? k : MAXLEVEL; return k; }
class node { public: node(int val, int level) { this->val = val; this->level = level; for (int i = 0; i < MAXLEVEL; ++i) { forward[i] = nullptr; } } ~node() {}
int val; int level; node* forward[MAXLEVEL]; };
class skiplist { public: skiplist() { max_length = 0; node* p = new node(0, MAXLEVEL); head = p; }
void insert(int val) { int level = randomLevel(); if (head == nullptr) { node* p = new node(val, MAXLEVEL); head = p; max_length++; } else { node* cur = head; node* p = new node(val, level); node* last[MAXLEVEL]; for (int i = MAXLEVEL-1; i >= 0; --i) { while (cur->forward[i] != nullptr && cur->forward[i]->val < val) { cur = cur->forward[i]; } last[i] = cur; } if (cur->forward[0] == nullptr) { max_length++; for (int i = p->level - 1; i >= 0; --i) { last[i]->forward[i] = p; } } else if (cur->forward[0]->val > val) { max_length++; for (int i = p->level - 1; i >= 0; --i) { p->forward[i] = last[i]->forward[i]; last[i]->forward[i] = p; } } } }
void search(int val) { node* cur = head; for (int i = MAXLEVEL-1; i >= 0; --i) { while (cur->forward[i] != nullptr && cur->forward[i]->val < val) { cur = cur->forward[i]; } if (cur->forward[i] != nullptr && cur->forward[i]->val == val) { cout << "search val = " << val << endl; } } cout << "not find val = " << val << endl; }
void del(int val) { node* cur = head; node* target = nullptr; node* last[MAXLEVEL]; for (int i = MAXLEVEL-1; i >= 0; --i) { while (cur->forward[i] != nullptr && cur->forward[i]->val < val) { cur = cur->forward[i]; } last[i] = cur; if (cur->forward[i] != nullptr && cur->forward[i]->val == val) { target = cur->forward[i]; } } if (target != nullptr) { for (int i = target->level - 1; i >= 0; --i) { last[i]->forward[i] = target->forward[i]; } delete(target); --max_length; cout << "delete val = " << val << endl; } }
int get_max_length() { return max_length; }
void print() { for (int i = MAXLEVEL-1; i >= 0; --i) { node* cur = head; while (cur->forward[i]) { cur = cur->forward[i]; cout << cur->val << " --> "; } cout << endl; } }
private: node* head; int max_length; };
int main() { skiplist sk1; sk1.insert(3); sk1.insert(7); sk1.insert(4); sk1.insert(6); sk1.insert(5); sk1.insert(-1); sk1.del(4); sk1.insert(6); sk1.search(8); sk1.del(8); sk1.print(); return 0; }
|
补充:
【问题】在Redis中,zset 是如何实现的?
当有序集合的结点个数 > 128 或者任意节点的 value 值 > 64 时,才会使用 skiplist 存储,否则使用 ziplist(字节数组) 存储。
【问题】Redis中,节点的层数如何确定?
限定最高层级。
| #define ZSKIPLIST_MAXLEVEL 32 int zslRandomLevel(void) { int level = 1; while ((random() & 0xFFFF) < (ZSKIPLIST_P * 0xFFFF)) level += 1; return (level < ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL; }
|
参考:
https://mp.weixin.qq.com/s/6rVgWdb8wG9d-xInkguv_w
https://blog.csdn.net/u013011841/article/details/39158585
https://blog.csdn.net/weixin_44866921/article/details/108956004
https://blog.csdn.net/weixin_45827856/article/details/103254965