跳表的实现
在计算机科学中,跳跃列表是一种数据结构。它使得包含 n 个元素的有序序列的查找和插入操作的平均时间复杂度都是$O(\log n)$,优于数组的 $O(n)$ 复杂度。
什么是跳表
跳表是对有序链表的改进, 在有序链表的基础上增加了 分层 的概念。首先,跳表的每一层都是一个有序链表,特别地,最底层是初始的有序链表。每个位于第 i
层的节点有 p 的概率出现在第 i+1
层,p 为常数(通常为 $\frac{1}{2}$ 或 $\frac{1}{4}$)。
跳表是是分层的链表
跳表的每一层都是有序链表
跳表的最底层是完整的有序链表
跳表的结构看起来像个树, 查询、插入和删除的复杂度也是 $O(\log{n})$ (最坏复杂度是 $O(n)$, 每层有哪些节点是通过概率得到的,因此最坏可能每一层都是一个完整的链表)。
具体实现
节点的设计
一个节点要存储这个节点所在的每一层的下个节点的指针,使用一个数组来存储 forward(Node **)
。
1 2 3 4 5 6 7 8 9 10
| struct Node { K key; V value; Node **forward; int level; Node(K key, V value, int level) : key(key), value(value), level(level) { forward = new Node *[level + 1]; } ~Node() { delete[] forward; } };
|
获取节点的最大层数
每个位于第 i 层的节点有 p 的概率出现在第 i+1 层
模拟这个过程即可
这里使用到了 rand()
这个随机函数, 为了保证随机性,同时也要使用设置 srand(time(0))
来保证随机性, 更推荐用 C++11 的随机数生成设施替换 rand() 。
1 2 3 4 5 6 7 8 9 10 11
| constexpr int MAX_LV = 32; constexpr int P = 4; constexpr int S = 0xFFFF; constexpr int PS = S / P;
auto random_level() -> int { int lv = 0; while ((rand() & S) < PS) ++lv; return MAX_LV > lv ? lv : MAX_LV; }
|
查找
在跳表中查找,就是从第 $L(n)$ 层开始,水平地逐个比较直至当前节点的下一个节点大于等于目标节点,然后移动至下一层。重复这个过程直至到达第一层且无法继续进行操作。此时,若下一个节点是目标节点,则成功查找;反之,则元素不存在。这样一来,查找的过程中会跳过一些没有必要的比较,所以相比于有序链表的查询,跳表的查询更快。
在这个 1-8 的跳表中查找 5 (这里演示的比较是严格小于)
- 开始是在第 3 层的 head 处,与他指向的下一个节点的值做比较 ($1 < 5$), 现在移动到了第 3 层的 1 节点处
- 1 节点第 3 层的下一个节点不存在,现在移动到 1 节点的第 2 层
- 与 1 节点第 2 层的下一个节点的值做比较 ($4 < 5$), 移动到 4 节点的第 2 层
- 与 4 节点第 2 层的下一个节点的值做比较 ($6 > 5$), 移动到 4 节点的第 1 层
- 与 4 节点第 1 层的下一个节点的值做比较 ($6 > 5$), 移动到 4 节点的第 0 层
- 与 4 节点第 0 层的下一个节点的值做比较 ($5 = 5$), 下一个节点就是我们要找的值,这里使用了严格小于的比较方式,最后获得的是正确结果的前一个节点。
答案就是查找到的下一个节点。
1 2 3 4 5 6 7 8 9 10 11 12
| auto find(K key) -> V { if (size == 0) return 0; Node *tmp = head; for (int i = level; i >= 0; --i) { while (tmp->forward[i] != nullptr && cmp(tmp->forward[i]->key, key)) tmp = tmp->forward[i]; } if (tmp->forward[0] != nullptr && tmp->forward[0]->key == key) return tmp->value; return V{}; }
|
插入
插入与查找的过程类似,不同的是需要记录每一层要插入哪个节点的后面(使用 update
记录)。
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
| Node **update{new Node *[MAX_LV + 1]};
auto insert(K key, V value) -> void { Node *tmp = head; for (int i = level; i >= 0; --i) { while (tmp->forward[i] != nullptr && cmp(tmp->forward[i]->key, key)) { tmp = tmp->forward[i]; } update[i] = tmp; } if (tmp->forward[0] != nullptr && tmp->forward[0]->key == key) { tmp->forward[0]->value = value; return; } auto lv = [&]() { int t = random_level(); if (level < t) { t = ++level; update[level] = head; } return t; }(); Node *new_node = new Node(key, value, lv); for (int i = lv; i >= 0; --i) { new_node->forward[i] = update[i]->forward[i]; update[i]->forward[i] = new_node; } ++size; }
|
删除
删除操作与插入操作相同,同样要记录这个节点在每一层的前一个节点。
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
| auto erase(K key) -> void { Node *tmp = head; for (int i = level; i >= 0; --i) { while (tmp->forward[i] != nullptr && cmp(tmp->forward[i]->key, key)) tmp = tmp->forward[i]; update[i] = tmp; } if (tmp->forward[0] == nullptr || tmp->forward[0]->key != key) { return; }
auto p = update[0]->forward[0];
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] == nullptr) --level; --size; }
|
完整代码
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
| constexpr int MAX_LV = 32; constexpr int P = 4; constexpr int S = 0xFFFF; constexpr int PS = S / P;
template <typename K, typename V, typename CMP = std::less<K>> struct SkipList {
struct Node { K key; V value; Node **forward; int level; Node(K key, V value, int level) : key(key), value(value), level(level) { forward = new Node *[level + 1]; } ~Node() { delete[] forward; } };
private: Node *head{new Node(K{}, V{}, MAX_LV)}; Node **update{new Node *[MAX_LV + 1]}; CMP cmp; int size{0}; int level{0};
public: SkipList() { srand(time(0)); }
~SkipList() { delete[] update; for (Node *tmp = head; tmp != nullptr;) { head = head->forward[0]; delete tmp; tmp = head; } }
auto random_level() -> int { int lv = 0; while ((rand() & S) < PS) ++lv; return MAX_LV > lv ? lv : MAX_LV; }
auto find(K key) -> V { if (size == 0) return 0; Node *tmp = head; for (int i = level; i >= 0; --i) { while (tmp->forward[i] != nullptr && cmp(tmp->forward[i]->key, key)) tmp = tmp->forward[i]; } if (tmp->forward[0] != nullptr && tmp->forward[0]->key == key) return tmp->value; return V{}; }
auto insert(K key, V value) -> void { Node *tmp = head; for (int i = level; i >= 0; --i) { while (tmp->forward[i] != nullptr && cmp(tmp->forward[i]->key, key)) { tmp = tmp->forward[i]; } update[i] = tmp; } if (tmp->forward[0] != nullptr && tmp->forward[0]->key == key) { tmp->forward[0]->value = value; return; }
auto lv = [&]() { int t = random_level(); if (level < t) { t = ++level; update[level] = head; } return t; }();
Node *new_node = new Node(key, value, lv); for (int i = lv; i >= 0; --i) { new_node->forward[i] = update[i]->forward[i]; update[i]->forward[i] = new_node; } ++size; }
auto erase(K key) -> void { Node *tmp = head; for (int i = level; i >= 0; --i) { while (tmp->forward[i] != nullptr && cmp(tmp->forward[i]->key, key)) tmp = tmp->forward[i]; update[i] = tmp; } if (tmp->forward[0] == nullptr || tmp->forward[0]->key != key) { return; }
auto p = update[0]->forward[0];
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] == nullptr) --level; --size; } };
|
其他
OI-WIKI 跳表
维基百科 跳跃列表