当前位置: 首页 > news >正文

高阶数据结构 --- 跳表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]);}}
}
http://www.dtcms.com/a/545320.html

相关文章:

  • Ansible模块分类与实战应用指南
  • 发送 Prompt 指令:请用一句话总结文本内容
  • 沧州网站seo创业 建网站
  • 临安市住房和建设局网站百度搜索引擎的原理
  • k8s rbac权限最小化实践
  • Javascript数据类型之类型转换
  • 销售拜访前的全面准备指南以及ABC推荐法
  • 优秀网站模板下载网站编程论文
  • 仓颉代码内联策略:性能优化的精密艺术
  • 欧瑞电机编码器引脚定义
  • 中国隧道空间分布
  • 作文网站哪个平台好wordpress超简洁主题
  • 聊城公司网站建设注册域名需要多久
  • 国外摄影网站合肥网站网站建设
  • Vue+Element 封装表格组件
  • 有向图能拓扑排序,必定无环
  • 网络:2.1加餐 - 网络命令
  • 为什么需要设置字符编码?
  • 电影网站如何做seo济南制作公司网站
  • 怎么网站排名seo乐清网络推广公司
  • 仓颉 String 内存表示:从 UTF-8 小对象到零拷贝子串的完整旅程
  • Android Studio新手开发第三十四天
  • 多维c++ vector, vector<pair<int,int>>, vector<vector<pair<int,int>>>示例
  • 【TVM 教程】自定义优化
  • 免费行情网站大全下载成品源码网站
  • 男女生做羞羞事情的网站网站域名怎样选择
  • 做政协网站软件的公司找人做网站 优帮云
  • 电力系统安全新样本:瑞数信息用“动态安全”筑起业务防线
  • 基于Python(Tkinter)实现(图形界面)小说阅读器
  • 选ThinkPad还是ThinkBook?联想乐享智能体让你告别选择困难!