├── README.md ├── main.cpp └── skiplist.h /README.md: -------------------------------------------------------------------------------- 1 | # Redis实现skipList(跳表) 2 | ## 项目介绍 3 | 非关系型数据库redis,以及levedb,rockdb其核心存储引擎的数据结构就是跳表。 4 | 5 | 本项目就是基于跳表实现的轻量级键值型存储引擎,使用C++实现。插入数据、删除数据、查询数据、数据展示、数据落盘、文件加载数据,以及数据库大小显示。 6 | 7 | ## 函数提供接口 8 | int insert_element(K,V); (插入数据) 9 | void display_list(); (展示跳表中数据) 10 | bool search_element(K); (搜索数据) 11 | void delete_element(K); (删除数据) 12 | void dump_file(); (读取数据) 13 | void load_file(); (存放数据) 14 | int size(); (元素数量) 15 | 16 | ## 跳表原理解释 17 | ### 什么是跳表 18 | 单链表是是一种各性能比较优秀的动态数据结构,可以支持快速的插入、删除、查找操作。 19 | 即便在有序的单链表中,插入、删除操作仍然时间复杂度为O(N),那么有没有更好的优化方法呢? 20 | 跳表是在单链表的基础上进行对数据结构的优化,将插入、删除、查找时间复杂度都控制在O(log N)。我们这主要解释跳表原理和怎么讲单链表的插入、删除操作时间复杂度降低到O(log N) 21 | 22 | 我们可以按照元素的个数(n)分成好多层,每一层都有对应元素的索引。例如第一层就是原始单链表,第一层索引个数就是n,然而到了第二层,每个元素都有50%的几率上升到第二层。(***解释:咱们要求插入、删除操作都控制在O(log N),每个元素都是随机,这个时间复杂度是O(1),如果隔一元素上升一层的话,要加判定条件,就需要时间复杂度为O(N)***) 23 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/b21bff5f38a54e2da6a5296c2b3db244.jpeg#pic_center) 24 | 下列是按照最优操作建的图 25 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/d3fc775551c64ab7bf2321ab342f3e85.jpeg#pic_center) 26 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/0e238bdf25af49a2b7bc792f9189e606.jpeg#pic_center) 27 | 查找元素时,就是从顶层索引开始,对于每一层,大于当前索引对应的值,小于下层对应的值,开始执行下一层的操作。 28 | 29 | 30 | 当查找元素8时,按照二级索引,找到区间[ 7-12 ],执行下一层,执行[ 7-9 ]区间。继续执行下一层,找到元素8,只需要四次操作! 31 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/6f3117d8bd204aa9a9e257cd8b30a2c7.png) 32 | 33 | 但是现在发现如果按照单链表查找的8话也只需要5次操作,根本没有表示出跳表的强大魅力!!! 34 | 那接下来咱们试试更多元素的查找 35 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/3015bbbcdef84511ad7c7f0631e364e0.jpeg#pic_center)原始链表有64个元素,查找第60个元素,如果按照单链表查找需要60次操作,如果跳表操作的话就可以6次操作即可查找出来第60个元素,符合时间复杂度O(log N)。 36 | 这次是不是就可以体现跳表的强大能力啦!!! 37 | 38 | ### 分析跳表插入、删除、查找时间复杂度 39 | 插入、删除、查找在跳表中都是按照索引的方式查找,可以说三种方式几乎一样。以查找为例: 40 | 单链表元素个数为16,按照每元素50%的几率上升一层 41 | 0级(原始链表)索引数量 : 16 42 | 1级索引数量:8 43 | 2级索引数量:4 44 | 3级索引数量:2 45 | 基本可以确定索引数量按照层数以log次方递减。 46 | 47 | ### 分析跳表的空间复杂度 48 | 跳表的时间复杂度为O(log N),空间复杂度是拿空间换时间的操作,索引个数随着层数减半,成等比数列,空间复杂度为O(N)。 49 | 50 | ### 跳表索引更新 51 | 从上面插入元素8的过程中发现,我们插入8时没有更新索引,会出现 2 个索引结点之间数据非常多的情况,若频繁的插入数据,但不更新索引,最终会退化成单链表的数据结构,会导致查找数据效率变低。 52 | 53 | 跳表作为一个动态的数据结构,需要动态的维护索引与原始链表中的大小。若原始链表插入的结点变多了,那么相应的索引结点也需要增加,避免查找、删除、插入的性能下降。 54 | 其实是通过一个随机函数,来决定将这个结点插入到哪几级索引中,比如随机函数生成了值rand,那就将这个结点添加到第一级到第rand级这rand级索引中。 55 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | //所有函数的解释与用法都在skiplist.h中,main主函数主要用于测试各种函数是否可行 2 | 3 | #include 4 | #include "skiplist.h" 5 | #define FILE_PATH "./store/dumpFile" 6 | int main() 7 | { 8 | SkipListskipList(6); 9 | skipList.insert_element(1,"学习"); 10 | skipList.insert_element(3,"跳表"); 11 | skipList.insert_element(7,"去找"); 12 | skipList.insert_element(8,"GitHub:"); 13 | skipList.insert_element(9,"shy2593666979"); 14 | skipList.insert_element(19,"赶紧给个"); 15 | skipList.insert_element(19,"star!"); 16 | std::cout<<"skipList.size = "< 13 | class Node{ 14 | public: 15 | Node(){} 16 | Node(K k,V v,int); 17 | ~Node(); 18 | K get_key() const; 19 | V get_value() const; 20 | void set_value(V); 21 | 22 | Node **forward; //forward是指针数组,用于指向下一层 例如 forward[0]是指向第一层,forward[1]指向上一层 23 | int node_level; 24 | private: 25 | K key; 26 | V value; 27 | }; 28 | template 29 | Node::Node(const K k, const V v, int level) 30 | { 31 | this->key=k; 32 | this->value=v; 33 | this->node_level=level; 34 | this->forward=new Node *[level+1]; 35 | memset(this->forward,0,sizeof(Node*)*(level+1)); 36 | }; 37 | template 38 | Node::~Node() 39 | { 40 | delete []forward; 41 | }; 42 | template 43 | K Node::get_key() const { 44 | return key; 45 | }; 46 | template 47 | V Node::get_value() const { 48 | return value; 49 | }; 50 | template 51 | void Node::set_value(V value) 52 | { 53 | this->value=value; 54 | }; 55 | template 56 | class SkipList{ 57 | public: 58 | SkipList(int); 59 | ~SkipList(); 60 | int get_random_level(); 61 | Node*create_node(K,V,int); 62 | int insert_element(K,V); 63 | void display_list(); 64 | bool search_element(K); 65 | void delete_element(K); 66 | void dump_file(); 67 | void load_file(); 68 | int size(); 69 | private: 70 | void get_key_value_from_string(const std::string &str,std::string*key,std::string *value); 71 | bool is_valid_string(const std::string &str); 72 | private: 73 | int _max_level; //跳表的最大层级 74 | int _skip_list_level; //当前跳表的有效层级 75 | Node *_header; //表示跳表的头节点 76 | std::ofstream _file_writer; //默认以输入(writer)方式打开文件。 77 | std::ifstream _file_reader; //默认以输出(reader)方式打开文件。 78 | int _element_count; //表示跳表中元素的数量 79 | }; 80 | 81 | //create_node函数:根据给定的键、值和层级创建一个新节点,并返回该节点的指针 82 | template 83 | Node *SkipList::create_node(const K k, const V v, int level) 84 | { 85 | Node*n=new Node(k,v,level); 86 | return n; 87 | } 88 | 89 | //insert_element 函数:插入一个新的键值对到跳表中。通过遍历跳表,找到插入位置,并根据随机层级创建节点。 90 | //如果键已存在,则返回 1,表示插入失败;否则,插入成功,返回 0。 91 | template 92 | int SkipList::insert_element(const K key,const V value) 93 | { 94 | mtx.lock(); 95 | Node *current=this->_header; 96 | Node *update[_max_level]; 97 | memset(update,0,sizeof(Node*)*(_max_level+1)); 98 | //99-113行-为查找key是否在跳表中出现,也可以直接调用search_element(K key) 99 | for(int i=_skip_list_level;i>=0;i--) 100 | { 101 | while(current->forward[i]!=NULL&¤t->forward[i]->get_key()forward[i]; 104 | } 105 | update[i]=current; //update是存储每一层需要插入点节点的位置 106 | } 107 | current=current->forward[0]; 108 | if(current!=NULL&¤t->get_key()==key) 109 | { 110 | std::cout<<"key:"<get_key()!=key) 117 | { 118 | int random_level=get_random_level(); 119 | if(random_level>_skip_list_level) 120 | { 121 | for(int i=_skip_list_level+1;i*inserted_node= create_node(key,value,random_level); 128 | for(int i=0;iforward[i]=update[i]->forward[i]; //跟链表的插入元素操作一样 131 | update[i]->forward[i]=inserted_node; 132 | } 133 | std::cout<<"Successfully inserted key:"< 142 | void SkipList::display_list() 143 | { 144 | std::cout<<"\n*****SkipList*****"<<"\n"; 145 | for(int i=0;i<_skip_list_level;i++) 146 | { 147 | Node*node=this->_header->forward[i]; 148 | std::cout<<"Level"<get_key()<<":"<get_value()<<";"; 152 | node=node->forward[i]; 153 | } 154 | std::cout< 161 | void SkipList::dump_file() 162 | { 163 | std::cout<<"dump_file-----------"<*node=this->_header->forward[0]; 166 | while(node!=NULL) 167 | { 168 | _file_writer<get_key()<<":"<get_value()<<"\n"; 169 | std::cout<get_key()<<":"<get_value()<<"\n"; 170 | node=node->forward[0]; 171 | } 172 | _file_writer.flush(); //设置写入文件缓冲区函数 173 | _file_writer.close(); 174 | return ; 175 | } 176 | 177 | //将文件中的内容转到跳表中、每一行对应的是一组数据,数据中有:分隔,还需要get_key_value_from_string(line,key,value)将key和value分开。 178 | //直到key和value为空时结束,每组数据分开key、value后通过insert_element()存到跳表中来 179 | template 180 | void SkipList::load_file() 181 | { 182 | _file_reader.open(STORE_FILE); 183 | std::cout<<"load_file----------"<empty()||value->empty()) 191 | { 192 | continue; 193 | } 194 | int target=0; 195 | std::string str_key=*key; //当时定义的key为int类型,所以将得到的string类型的 key转成int 196 | for(int i=0;i 208 | int SkipList::size() { 209 | return _element_count; 210 | } 211 | 212 | //从STORE_FILE文件读取时,每一行将key和value用 :分开,此函数将每行的key和value分割存入跳表中 213 | template 214 | void SkipList::get_key_value_from_string(const std::string &str, std::string *key, std::string *value) 215 | { 216 | if(!is_valid_string(str)) return ; 217 | *key=str.substr(0,str.find(delimiter)); 218 | *value=str.substr(str.find(delimiter)+1,str.length()); 219 | } 220 | 221 | //判断从get_key_value_from_string函数中分割的字符串是否正确 222 | template 223 | bool SkipList::is_valid_string(const std::string &str) 224 | { 225 | if(str.empty()) 226 | { 227 | return false; 228 | } 229 | if(str.find(delimiter)==std::string::npos) 230 | { 231 | return false; 232 | } 233 | return true; 234 | } 235 | 236 | //遍历跳表找到每一层需要删除的节点,将前驱指针往前更新,遍历每一层时,都需要找到对应的位置 237 | //前驱指针更新完,还需要将全为0的层删除 238 | template 239 | void SkipList::delete_element(K key) 240 | { 241 | mtx.lock(); 242 | Node*current=this->_header; 243 | Node*update[_max_level+1]; 244 | memset(update,0,sizeof(Node*)*(_max_level+1)); 245 | for(int i=_skip_list_level;i>=0;i--) 246 | { 247 | while(current->forward[i]!=NULL&¤t->forward[i]->get_key()forward[i]; 250 | } 251 | update[i]=current; 252 | } 253 | current=current->forward[0]; 254 | if(current!=NULL&¤t->get_key()==key) 255 | { 256 | for(int i=0;i<=_skip_list_level;i++) { 257 | if (update[i]->forward[i] != current) { 258 | break; 259 | } 260 | update[i]->forward[i] = current->forward[i]; 261 | } 262 | while(_skip_list_level>0&&_header->forward[_skip_list_level]==0) 263 | { 264 | _skip_list_level--; 265 | } 266 | std::cout<<"Successfully deleted key"< 276 | bool SkipList::search_element(K key) 277 | { 278 | std::cout<<"search_element------------"< *current=_header; 280 | for(int i=_skip_list_level;i>=0;i--) 281 | { 282 | while(current->forward[i]&¤t->forward[i]->get_key()forward[i]; 285 | } 286 | } 287 | current=current->forward[0]; 288 | if(current and current->get_key()==key) 289 | { 290 | std::cout<<"Found key:"<get_value()< 297 | 298 | SkipList::SkipList(int max_level) 299 | { 300 | this->_max_level=max_level; 301 | this->_skip_list_level=0; 302 | this->_element_count=0; 303 | K k; 304 | V v; 305 | this->_header=new Node(k,v,_max_level); 306 | }; 307 | //释放内存,关闭_file_writer _file_reader 308 | template 309 | SkipList::~SkipList() 310 | { 311 | if(_file_writer.is_open()) 312 | { 313 | _file_writer.close(); 314 | } 315 | if(_file_reader.is_open()) 316 | { 317 | _file_reader.close(); 318 | } 319 | delete _header; 320 | } 321 | //生成一个随机层级。从第一层开始,每一层以 50% 的概率加入 322 | template 323 | int SkipList::get_random_level() 324 | { 325 | int k=1; 326 | while(rand()%2) 327 | { 328 | k++; 329 | } 330 | k=(k<_max_level)?k:_max_level; 331 | return k; 332 | }; 333 | 334 | --------------------------------------------------------------------------------