Redis实现skipList(跳表) 代码有详解

Redis实现skipList(跳表)

项目介绍

非关系型数据库redis,以及levedb,rockdb其核心存储引擎的数据结构就是跳表。

本项目就是基于跳表实现的轻量级键值型存储引擎,使用C++实现。插入数据、删除数据、查询数据、数据展示、数据落盘、文件加载数据,以及数据库大小显示。

函数提供接口

int insert_element(K,V); (插入数据)
void display_list(); (展示跳表中数据)
bool search_element(K); (搜索数据)
void delete_element(K); (删除数据)
void dump_file(); (读取数据)
void load_file(); (存放数据)
int size(); (元素数量)

跳表原理解释

什么是跳表

单链表是是一种各性能比较优秀的动态数据结构,可以支持快速的插入、删除、查找操作。
即便在有序的单链表中,插入、删除操作仍然时间复杂度为O(N),那么有没有更好的优化方法呢?
跳表是在单链表的基础上进行对数据结构的优化,将插入、删除、查找时间复杂度都控制在O(log N)。我们这主要解释跳表原理和怎么讲单链表的插入、删除操作时间复杂度降低到O(log N)

我们可以按照元素的个数(n)分成好多层,每一层都有对应元素的索引。例如第一层就是原始单链表,第一层索引个数就是n,然而到了第二层,每个元素都有50%的几率上升到第二层。(解释:咱们要求插入、删除操作都控制在O(log N),每个元素都是随机,这个时间复杂度是O(1),如果隔一元素上升一层的话,要加判定条件,就需要时间复杂度为O(N)
在这里插入图片描述
下列是按照最优操作建的图
在这里插入图片描述
在这里插入图片描述
查找元素时,就是从顶层索引开始,对于每一层,大于当前索引对应的值,小于下层对应的值,开始执行下一层的操作。

当查找元素8时,按照二级索引,找到区间[ 7-12 ],执行下一层,执行[ 7-9 ]区间。继续执行下一层,找到元素8,只需要四次操作!
在这里插入图片描述

但是现在发现如果按照单链表查找的8话也只需要5次操作,根本没有表示出跳表的强大魅力!!!
那接下来咱们试试更多元素的查找
在这里插入图片描述原始链表有64个元素,查找第60个元素,如果按照单链表查找需要60次操作,如果跳表操作的话就可以6次操作即可查找出来第60个元素,符合时间复杂度O(log N)。
这次是不是就可以体现跳表的强大能力啦!!!

分析跳表插入、删除、查找时间复杂度

插入、删除、查找在跳表中都是按照索引的方式查找,可以说三种方式几乎一样。以查找为例:
单链表元素个数为16,按照每元素50%的几率上升一层
0级(原始链表)索引数量 : 16
1级索引数量:8
2级索引数量:4
3级索引数量:2
基本可以确定索引数量按照层数以log次方递减。

分析跳表的空间复杂度

跳表的时间复杂度为O(log N),空间复杂度是拿空间换时间的操作,索引个数随着层数减半,成等比数列,空间复杂度为O(N)。

跳表索引更新

从上面插入元素8的过程中发现,我们插入8时没有更新索引,会出现 2 个索引结点之间数据非常多的情况,若频繁的插入数据,但不更新索引,最终会退化成单链表的数据结构,会导致查找数据效率变低。

跳表作为一个动态的数据结构,需要动态的维护索引与原始链表中的大小。若原始链表插入的结点变多了,那么相应的索引结点也需要增加,避免查找、删除、插入的性能下降。
其实是通过一个随机函数,来决定将这个结点插入到哪几级索引中,比如随机函数生成了值rand,那就将这个结点添加到第一级到第rand级这rand级索引中。

代码源码

skipList.h包括函数的源码及详解

#include<iostream>
#include<cmath>
#include<cstring>
#include<mutex>
#include<fstream>

#define STORE_FILE "store/dumpFile"

std::mutex mtx;  //代表互斥锁 ,保持线程同步
std::string delimiter=":";  //存放到STORE_FILE中时,将delimiter也存入进文件中,用于get_key_value_from_string的key与value区分

template<typename K,typename V>
class Node{
    
    
public:
    Node(){
    
    }
    Node(K k,V v,int);
    ~Node();
    K get_key() const;
    V get_value() const;
    void set_value(V);

    Node <K,V> **forward;  //forward是指针数组,用于指向下一层 例如  forward[0]是指向第一层,forward[1]指向上一层
    int node_level;
private:
     K key;
     V value;
};
template<typename K,typename V>
Node<K,V>::Node(const K k, const V v, int level)
{
    
    
    this->key=k;
    this->value=v;
    this->node_level=level;
    this->forward=new Node<K,V> *[level+1];
    memset(this->forward,0,sizeof(Node<K,V>*)*(level+1));
};
template<typename  K,typename V>
Node<K,V>::~Node()
{
    
    
    delete []forward;
};
template<typename K,typename V>
K Node<K,V>::get_key() const {
    
    
    return key;
};
template<typename K,typename V>
V Node<K,V>::get_value() const {
    
    
    return value;
};
template<typename K,typename V>
void Node<K,V>::set_value(V value)
{
    
    
    this->value=value;
};
template<typename K,typename V>
class SkipList{
    
    
public:
    SkipList(int);
    ~SkipList();
    int get_random_level();
    Node<K,V>*create_node(K,V,int);
    int insert_element(K,V);
    void display_list();
    bool search_element(K);
    void delete_element(K);
    void dump_file();
    void load_file();
    int size();
private:
    void get_key_value_from_string(const std::string &str,std::string*key,std::string *value);
    bool is_valid_string(const std::string &str);
private:
    int _max_level;              //跳表的最大层级
    int _skip_list_level;        //当前跳表的有效层级
    Node<K,V> *_header;          //表示跳表的头节点
    std::ofstream _file_writer;  //默认以输入(writer)方式打开文件。
    std::ifstream _file_reader;  //默认以输出(reader)方式打开文件。
    int _element_count;          //表示跳表中元素的数量
};

//create_node函数:根据给定的键、值和层级创建一个新节点,并返回该节点的指针
template<typename K,typename V>
Node<K,V> *SkipList<K,V>::create_node(const K k, const V v, int level)
{
    
    
    Node<K,V>*n=new Node<K,V>(k,v,level);
    return n;
}

//insert_element 函数:插入一个新的键值对到跳表中。通过遍历跳表,找到插入位置,并根据随机层级创建节点。
//如果键已存在,则返回 1,表示插入失败;否则,插入成功,返回 0。
template<typename K,typename V>
int SkipList<K,V>::insert_element(const K key,const  V value)
{
    
    
    mtx.lock();
    Node<K,V> *current=this->_header;
    Node<K,V> *update[_max_level];
    memset(update,0,sizeof(Node<K,V>*)*(_max_level+1));
      //99-113行-为查找key是否在跳表中出现,也可以直接调用search_element(K key)
    for(int i=_skip_list_level;i>=0;i--)
    {
    
    
        while(current->forward[i]!=NULL&&current->forward[i]->get_key()<key)
        {
    
    
            current=current->forward[i];
        }
        update[i]=current;   //update是存储每一层需要插入点节点的位置
    }
    current=current->forward[0];
    if(current!=NULL&&current->get_key()==key)
    {
    
    
        std::cout<<"key:"<<key<<",exists"<<std::endl;
        mtx.unlock();
        return 1;
    }

    //添加的值没有在跳表中
    if(current==NULL||current->get_key()!=key)
    {
    
    
        int random_level=get_random_level();
        if(random_level>_skip_list_level)
        {
    
    
            for(int i=_skip_list_level+1;i<random_level+1;i++)
            {
    
    
                update[i]=_header;
            }
            _skip_list_level=random_level;
        }
        Node<K,V>*inserted_node= create_node(key,value,random_level);
        for(int i=0;i<random_level;i++)
        {
    
    
            inserted_node->forward[i]=update[i]->forward[i];  //跟链表的插入元素操作一样
            update[i]->forward[i]=inserted_node;
        }
        std::cout<<"Successfully inserted key:"<<key<<",value:"<<value<<std::endl;
        _element_count++;
    }
    mtx.unlock();
    return 0;
}

//display_list函数:输出跳表包含的内容、循环_skip_list_level(有效层级)、从_header头节点开始、结束后指向下一节点
template<typename K,typename V>
void SkipList<K,V>::display_list()
{
    
    
    std::cout<<"\n*****SkipList*****"<<"\n";
    for(int i=0;i<_skip_list_level;i++)
    {
    
    
        Node<K,V>*node=this->_header->forward[i];
        std::cout<<"Level"<<i<<":";
        while(node!=NULL)
        {
    
    
            std::cout<<node->get_key()<<":"<<node->get_value()<<";";
            node=node->forward[i];
        }
        std::cout<<std::endl;
    }
}

//dump_file 函数:将跳跃表的内容持久化到文件中。遍历跳跃表的每个节点,将键值对写入文件。
//其主要作用就是将跳表中的信息存储到STORE_FILE文件中,node指向forward[0],每一次结束后再将node指向node.forward[0]。
template<typename K,typename V>
void SkipList<K,V>::dump_file()
{
    
    
    std::cout<<"dump_file-----------"<<std::endl;
    _file_writer.open(STORE_FILE);
    Node<K,V>*node=this->_header->forward[0];
    while(node!=NULL)
    {
    
    
        _file_writer<<node->get_key()<<":"<<node->get_value()<<"\n";
        std::cout<<node->get_key()<<":"<<node->get_value()<<"\n";
        node=node->forward[0];
    }
    _file_writer.flush();  //设置写入文件缓冲区函数
    _file_writer.close();
    return ;
}

//将文件中的内容转到跳表中、每一行对应的是一组数据,数据中有:分隔,还需要get_key_value_from_string(line,key,value)将key和value分开。
//直到key和value为空时结束,每组数据分开key、value后通过insert_element()存到跳表中来
template<typename K,typename V>
void SkipList<K,V>::load_file()
{
    
    
    _file_reader.open(STORE_FILE);
    std::cout<<"load_file----------"<<std::endl;
    std::string line;
    std::string *key=new std::string();
    std::string *value=new std::string();
    while(getline(_file_reader,line))
    {
    
    
        get_key_value_from_string(line,key,value);
        if(key->empty()||value->empty())
        {
    
    
            continue;
        }
        int target=0;
        std::string str_key=*key;   //当时定义的key为int类型,所以将得到的string类型的 key转成int
        for(int i=0;i<str_key.size();i++)
        {
    
    
            target=target*10+str_key[i]-'0';
        }
        int Yes_No=insert_element(target,*value);
        std::cout<<"key:"<<*key<<"value:"<<*value<<std::endl;
    }
    _file_reader.close();
}

//表示跳表中元素的数量
template<typename K,typename V>
int SkipList<K,V>::size() {
    
    
    return _element_count;
}

//从STORE_FILE文件读取时,每一行将key和value用 :分开,此函数将每行的key和value分割存入跳表中
template<typename K,typename V>
void SkipList<K,V>::get_key_value_from_string(const std::string &str, std::string *key, std::string *value)
{
    
    
    if(!is_valid_string(str)) return ;
    *key=str.substr(0,str.find(delimiter));
    *value=str.substr(str.find(delimiter)+1,str.length());
}

//判断从get_key_value_from_string函数中分割的字符串是否正确
template<typename K,typename V>
bool SkipList<K,V>::is_valid_string(const std::string &str)
{
    
    
    if(str.empty())
    {
    
    
        return false;
    }
    if(str.find(delimiter)==std::string::npos)
    {
    
    
        return false;
    }
    return true;
}

//遍历跳表找到每一层需要删除的节点,将前驱指针往前更新,遍历每一层时,都需要找到对应的位置
//前驱指针更新完,还需要将全为0的层删除
template<typename K,typename V>
void SkipList<K,V>::delete_element(K key)
{
    
    
    mtx.lock();
    Node<K,V>*current=this->_header;
    Node<K,V>*update[_max_level+1];
    memset(update,0,sizeof(Node<K,V>*)*(_max_level+1));
    for(int i=_skip_list_level;i>=0;i--)
    {
    
    
        while(current->forward[i]!=NULL&&current->forward[i]->get_key()<key)
        {
    
    
            current=current->forward[i];
        }
        update[i]=current;
    }
    current=current->forward[0];
    if(current!=NULL&&current->get_key()==key)
    {
    
    
        for(int i=0;i<=_skip_list_level;i++) {
    
    
            if (update[i]->forward[i] != current) {
    
    
                break;
            }
            update[i]->forward[i] = current->forward[i];
        }
            while(_skip_list_level>0&&_header->forward[_skip_list_level]==0)
            {
    
    
                _skip_list_level--;
            }
            std::cout<<"Successfully deleted key"<<key<<std::endl;
            _element_count--;
        }
        mtx.unlock();
        return ;
}

//遍历每一层,从顶层开始,找到每层对应的位置,然后进入下一层开始查找,直到查找到对应的key
//如果找到return true 输出Found  否则 return false ,输出Not Found
template<typename K,typename V>
bool SkipList<K,V>::search_element(K key)
{
    
    
    std::cout<<"search_element------------"<<std::endl;
    Node<K,V> *current=_header;
    for(int i=_skip_list_level;i>=0;i--)
    {
    
    
        while(current->forward[i]&&current->forward[i]->get_key()<key)
        {
    
    
            current=current->forward[i];
        }
    }
    current=current->forward[0];
    if(current and current->get_key()==key)
    {
    
    
        std::cout<<"Found key:"<<key<<",value:"<<current->get_value()<<std::endl;
        return true;
    }
    std::cout<<"Not Found Key:"<<key<<std::endl;
    return false;
}
template<typename K,typename V>

SkipList<K,V>::SkipList(int max_level)
{
    
    
    this->_max_level=max_level;
    this->_skip_list_level=0;
    this->_element_count=0;
    K k;
    V v;
    this->_header=new Node<K,V>(k,v,_max_level);
};
//释放内存,关闭_file_writer  _file_reader
template<typename K,typename V>
SkipList<K,V>::~SkipList()
{
    
    
    if(_file_writer.is_open())
    {
    
    
        _file_writer.close();
    }
    if(_file_reader.is_open())
    {
    
    
        _file_reader.close();
    }
    delete _header;
}
//生成一个随机层级。从第一层开始,每一层以 50% 的概率加入
template<typename K,typename V>
int SkipList<K,V>::get_random_level()
{
    
    
    int k=1;
    while(rand()%2)
    {
    
    
        k++;
    }
    k=(k<_max_level)?k:_max_level;
    return k;
};


main函数负责函数的调用测数据测试。

//所有函数的解释与用法都在skiplist.h中,main主函数主要用于测试各种函数是否可行

#include <iostream>
#include "skiplist.h"
#define FILE_PATH "./store/dumpFile"
int main()
{
    
    
    SkipList<int ,std::string>skipList(6);
    skipList.insert_element(1,"学习");
    skipList.insert_element(3,"跳表");
    skipList.insert_element(7,"去找");
    skipList.insert_element(8,"GitHub:");
    skipList.insert_element(9,"shy2593666979");
    skipList.insert_element(19,"赶紧给个");
    skipList.insert_element(19,"star!");
    std::cout<<"skipList.size = "<<skipList.size()<<std::endl;
    skipList.dump_file();
    skipList.search_element(8);
    skipList.search_element(9);
    skipList.display_list();
    skipList.delete_element(3);
    skipList.load_file();
    std::cout<<"skipList.size = "<<skipList.size()<<std::endl;
    skipList.display_list();
}

下载源码地址

Github下载地址(国际网站)

Gitee下载地址 (国内网站)

参考资料

https://github.com/youngyangyang04/Skiplist-CPP
https://juejin.cn/post/7149101822756519949

猜你喜欢

转载自blog.csdn.net/m0_63743577/article/details/131748260