高阶数据结构 --- 跳表Skiplist
Skiplist
- 一、跳表的由来
- 二、跳表的设计思路
- 2.1 设计思路
- 2.2 设计思路优化
- 2.3 随机层数的设计
- 三、跳表与平衡树、哈希表的对比
- 四、跳表代码实现(增删查)
一、跳表的由来

二、跳表的设计思路
2.1 设计思路


2.2 设计思路优化



2.3 随机层数的设计


三、跳表与平衡树、哈希表的对比

四、跳表代码实现(增删查)
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<vector>
#include<iostream>
#include <random>
#include <iomanip> // 用于控制输出精度
#include <cmath> // 用于round函数
#include<stack>
using namespace std;struct SkipListNode {int _val;vector<SkipListNode*> _vnext;int _level;SkipListNode(int val, int level):_val(val), _level(level), _vnext(level, nullptr){}
};
class Skiplist {
public:typedef SkipListNode Node;Skiplist(double p = 0.25, int MaxLevel = 32):_MaxLevel(MaxLevel), _p(p){_head = new Node(-1, 1); //头结点初始化为1层。头节点的层数,应当与跳表中最高层节点的层数保持一致}bool search(int target) {Node* pCur = _head;int plevel = pCur->_level - 1; //从最高层开始搜索while (1) {Node* next = pCur->_vnext[plevel];//目标值比下一个节点值小,或下一个节点为空,下降一层if (next == nullptr || next->_val > target) {if (plevel > 0) {//下降一层plevel--;}else { //已经是最底层,那么新节点应该插入到pCur节点后面!return false;}}//目标值比下一个节点值小大,向右走else if (next->_val < target) { //pCur向右走一步pCur = next;}else { //找到了return true;}}}void add(int num) {//1.对于插入一个元素,首先得找到该元素对应的插入位置// 而且,重要的是:在搜索的过程中,每次下降一层之前,先将本节点指针保存起来,以// 方便插入一个新节点之后,进行每一层的链表指向!!!stack<Node*> prevNodes;Node* pCur = _head;int plevel = pCur->_level - 1; //从最高层开始搜索//一定要注意的是:在新元素的插入位置时,一定要直到下降到第0层才能退出while (1)循环//因为我们更新每层链表节点的连接时,就是从第0层开始更新的。//当退出时,新节点的第0层一定是连接在此时的pCur的第0层后面。while (1) {Node* next = pCur->_vnext[plevel];//目标值比下一个节点值小或者等于,或下一个节点为空,下降一层if (next == nullptr || next->_val >= num) {if (plevel > 0) {//下降一层plevel--;//将pCur节点保存起来:if (prevNodes.size() == 0 || prevNodes.top() != pCur) { //不用重复保存(因为可能在同一个节点下降好几层)prevNodes.push(pCur);}}else { //已经是最底层,那么新节点应该插入到pCur节点后面!//将pCur节点保存起来:if (prevNodes.size() == 0 || prevNodes.top() != pCur) { //不用重复保存(因为可能在同一个节点下降好几层)prevNodes.push(pCur);}break;}}//目标值比下一个节点值小大,向右走else if (next->_val < num) { //pCur向右走一步pCur = next;}}//2.新节点//随机层数:int newnode_level = RandomLevel();Node* newnode = new Node(num, newnode_level);//3.开始链接://思路:从prevNodes的栈顶节点的第0层开始链接,直到链接到这个节点的最高层,假设为x(newnode_level>x)// 节点出栈,然后继续对栈顶节点进行链接,但是要注意:此时需要从该节点的第x+1层开始链接!!!// 一直这样,直到链接到newnode_level层int linklevel = 0;while (!prevNodes.empty()) { //假如newnode_level是2,那么实际链接的是第0、1层Node* prev = prevNodes.top();//当linklevel此时小于prev->_level,那么就要修改prev节点与newnode节点共同的第linklevel层的指向while (prev->_level > linklevel) {newnode->_vnext[linklevel] = prev->_vnext[linklevel];prev->_vnext[linklevel] = newnode;linklevel++;if (linklevel == newnode_level) { //linklevel最高是newnode_level-1break;}}if (linklevel == newnode_level) { //linklevel最高是newnode_level-1break;}prevNodes.pop();}//特殊情况:prevNodes已经空了,但是linklevel仍然小于newnode_level//这就说明:新节点的层数newnode_level比入栈的所有节点的层数都要高,但注意,这并不意味着比跳表的所有节点层数都高// 还得通过下面判断一下。// 假如_head的level大于newnode_level,那么剩余未更新的层,就由_head继续与newnode连接更新// 假如_head的level小于newnode_level,那么此时就要拉高头节点的层数,拉高至newnode_level,// 然后高出来的部分要连接这个新节点if (linklevel < newnode_level) {if (_head->_level < newnode_level) {_head->_vnext.resize(newnode_level);_head->_level = newnode_level;}while (linklevel < newnode_level) {_head->_vnext[linklevel] = newnode;linklevel++;}}}//删除节点的逻辑与添加节点的逻辑类似//首先都得找到被删除节点的位置,并记录这个过程中的下降节点,以便删除指定节点后,重新构建连接bool erase(int num) {stack<Node*> prevNodes;Node* pCur = _head;int plevel = pCur->_level - 1; //从最高层开始搜索//1.记录所有下降时的节点,找出目标删除节点while (1) {Node* next = pCur->_vnext[plevel];//目标值比下一个节点值小或者等于,或下一个节点为空,下降一层if (next == nullptr || next->_val >= num) {if (plevel > 0) {//下降一层plevel--;//将pCur节点保存起来:if (prevNodes.size() == 0 || prevNodes.top() != pCur) { //不用重复保存(因为可能在同一个节点下降好几层)prevNodes.push(pCur);}}else { //已经是最底层,那么新节点应该插入到pCur节点后面!//将pCur节点保存起来:if (prevNodes.size() == 0 || prevNodes.top() != pCur) { //不用重复保存(因为可能在同一个节点下降好几层)prevNodes.push(pCur);}break;}}//目标值比下一个节点值小大,向右走else if (next->_val < num) { //pCur向右走一步pCur = next;}}//被删除节点一定是此时栈顶节点的下一个节点!Node* del_node = prevNodes.top()->_vnext[0];if (del_node==nullptr || del_node->_val != num) { //除非这个节点为空,或者数字不是numreturn false;}//2. 删除节点并进行前后连接:int linklevel = 0;while (!prevNodes.empty()) { //假如del_node的层数是2,那么实际需要链接的是第0、1层Node* prev = prevNodes.top();//当linklevel此时小于prev->_level,那么就要修改prev节点与newnode节点共同的第linklevel层的指向while (prev->_level > linklevel) {//prev节点的linklevel层连接del_node的后一个节点prev->_vnext[linklevel] = del_node->_vnext[linklevel];linklevel++;if (linklevel == del_node->_level) { //linklevel最高是del_node->_levelbreak;}}if (linklevel == del_node->_level) { //linklevel最高是del_node->_levelbreak;}prevNodes.pop();}delete del_node;return true;}
private://生成一个大小介于[0,1]之间的随机数double Random() {// 使用硬件熵源初始化随机种子std::random_device rd;// 创建梅森旋转引擎(Mersenne Twister)std::mt19937 gen(rd());// 定义[0,1]区间的均匀分布// 使用nextafter确保包含1.0std::uniform_real_distribution<double> dis(0.0,std::nextafter(1.0, std::numeric_limits<double>::max()));// 生成随机数并四舍五入到小数点后两位double random_value = dis(gen);double rounded_value = std::round(random_value * 100.0) / 100.0;return rounded_value;}int RandomLevel() {//方法1:int v = 1;while (Random() < _p && v < _MaxLevel) {v++;}//std::cout << v << " ";return v;//方法2:更简单的写法:/*int v = 1;rand(time(nullptr));while (rand() < RAND_MAX * _p && v < _MaxLevel) {v++;}std::cout << v << " ";return v;*/}
private://哨兵位头节点Node* _head;int _MaxLevel; //节点最大层数double _p; //节点升高一层的概率
};//测试:
void test() {Skiplist sl(0.45, 7);//vector<int> arr = { 8,5,19,23,7,18,11,8,98,65,27,5,8,6,18,18,19,54,22,44,19,89,6,1,18,27,6,7,9 };vector<int> arr = { 5,19,19 };for (auto& num : arr) {sl.add(num);}/*vector<int> arr1 = { 8,5,19,23,7,18,11,98,65,27,6,54,22,89,1, 100,33,77,88,99,999,101,102,35,2,3,4};for (auto& num : arr1) {if (!sl.search(num)) {std::cout << num << " ";}}*/sl.search(10);/*if (sl.search(8)) {std::cout << 8 << " ";}sl.erase(8);if (sl.search(8)) {std::cout << 8 << " ";}*/
}
void test1() {Skiplist sl(0.25, 7);vector<string> v = {"add", "add", "add", "add", "add", "add", "add", "add", "add", "erase","search", "add", "erase", "erase", "erase", "add", "search", "search", "search", "erase","search", "add", "add", "add", "erase", "search", "add", "search", "erase", "search","search", "erase", "erase", "add", "erase", "search", "erase", "erase", "search", "add","add", "erase", "erase", "erase", "add", "erase", "add", "erase", "erase", "add", "add", "add","add", "erase", "add", "add", "add", "erase", "erase" };vector<int> vv = { 16,5,14,13,0,3,12,9,12,3,6,7,0,1,10,5,12,7,16,7,0,9,16,3,2,17,2,17,0,9,14,1,6,1,16,9,10,9,2,3,16,15,12,7,4,3,2,1,14,13,12,3,2,3,11,0,2,10,17 };for (int i = 0; i < v.size(); i++) {if (i == v.size() - 1 || i == v.size() - 2) {int x = 0;int y = 0;}if (v[i] == "add") {sl.add(vv[i]);}else if (v[i] == "erase") {sl.erase(vv[i]);}else {sl.search(vv[i]);}}
}
