Skiplist(跳表)的简单介绍与C++实现

0X 00 前言

前几天在看Redis的时候,看到网上说,Redis采用的是 Skiplist 而不是红黑树,但当时就给我整懵了。

啥子东西叫Skiplist,一查,翻译还挺直白——跳表。看起来很高级啊,于是嗖嗖嗖马上查资料

0X 10 Skiplist的设计初衷

在看到Skiplist庐山真面目之前,我们先来了解它是怎么出现的,或者说为何要出现

链表想必大家都熟悉,一般来讲,它长这样:
在这里插入图片描述
上面是一个有序链表,我们知道链表有一个好处就是在删除和插入的时候很方便。

但是,美中不足的就是,查询是它的一个软肋。

因为我们在链表中查找,只能从头开始一个一个往下找,所以一般来讲,链表的查找效率是 O(n)

所以基于这一点,有大神就开始打算盘了。如何很将链表的便捷与数组的查找优势(说的是有序数组下的二分查找)结合起来?

于是,于是,神奇的跳表就出来了(大神不愧是大神,分分钟出来:) )

0X 20 Skiplist的真面目

上面的想法很好啊,但是是不是很难实现呢?

只能说只有我们想不到,没有大神做不到

跳表跳表,最主要的就是在一个字上面,那么靠什么来跳呢——索引,说白了就是指针

好嘛,也是以空间换效率(要不然捏,大神可不是真神仙,能凭空变出来)

好了,铺垫差不多了,也该“千呼万唤始出来”了

上图!
在这里插入图片描述
(是不是看起来很眼熟,没错,没准你们数据结构老师上课讲过!)

看起来挺古怪的啊,不过人家搜索起来可是杠杠的

来个例子
假设我们需要查找25,如果是一般的有序链表我们需要比较几次呢?没做,9次,25前面的全要遍历一遍

那么采用跳表呢,怎么搜索,又要多少次呢?

敲黑板,重点来了!

来我们开始

  • 1、将当前指针curr指向头结点,然后查看最上面的指针指向的节点——21,第一次比较,21比25小,于是当前curr指向21这个节点
  • 2、故技重施,再看21的第一个最上面的指针,指向NULL,说明到头了,不行,第二次比较
  • 3、这时候,往下移一层,还是指向NULL,再移,第三次比较
  • 4、第二层(从下往上数)指向的是26这个节点,第四次比较,比25大,说明还是要往下走
  • 5、到了最后一层,这时候是就是,不是就不是了,于是进行第5次比较,25找到了

怎么样,从开始的9次变成了5次,差不过快了一半了吧

0X 30 Skiplist的庐山真面目

刚才我们也看到了,跳表还是很有用的。但是,其实刚才我们看到的跳表,并不是真正的跳表,为什么呢?

如果你认真观察的话,你会发现,上面的跳表有着严格的规定:上面一层的节点个数是下面一层个数的1/2

这样还是很有问题,比如当你插入一个节点的时候,那么结构不就破坏了?或者是又要用一定的办法来更改从而维持这样的结构。这样就和红黑树差不多了啊

大神的解决办法是什么呢?那就是不要”规定“,当然我们所的是不要上面那种严格的规定,而规则变成了一种随机

上下层不强制要求 1:2 ,一个节点要不要被索引,建几层的索引,都在节点插入时由抛硬币决定

当然,虽然索引的节点、索引的层数是随机的,为了保证搜索的效率,要大致保证每层的节点数目与上节的结构相当,因此随机出现的真正意义上的跳表是这样的:
在这里插入图片描述
发现比例已经不是严格的 1:2 了,但是总体来看还是比较规整的

下面是节点 17 插入的过程在这里插入图片描述
性能分析就不做啦(因为我不会2333),但是需要记住的是,跳表的 插入/删除/查找 都是 O(logn)

0X 30 硬核编码——Skiplist的C++实现

PS:以下代码99.99% 为copy(当然不是直接copy,是自己的一点点对着代码码上去的,然后加了点注释),但是希望大家能好好学习,自己敲一遍消化一下
可能敲的过程中有所纰漏,所以请大家多多留心!(后面会有代码出处链接)

skiplist.h

#ifndef _SKIPLSIT_H
#define _SKIPLSIT_H

#include <iostream>
#include <sstream>

template <typename K,typename V,int MAXLEVEL>

class skiplist_node	// 跳表的节点类 
{
private:
    K key;
    V value;
    skiplist_node<K,V,MAXLEVEL>* forwards;
public:
    skiplist_node()
    {
        forwards=new skiplist_node<K,V,MAXLEVEL> [MAXLEVEL];
        for(int i=0;i<MAXLEVEL;i++)
            forwards[i]=nullptr;
    }

    skiplist_node(K searchKey):key(searchKey)
    {
        for(int i=0;i<MAXLEVEL;i++)
            forwards[i]=nullptr;
    }

    ~skiplist_node()
    {
        delete [] forwards;
    }
};


template<typename K,typename V,MAXLEVEL=16>
class skiplist	//跳表类
{     
    
    protected:
        K m_minKey;	//最小键值
        K m_maxKey;	//最大键值
        int man_curr_level;	//当前最大层数
        skiplist_node<K,V,MAXLEVEL>* m_pHeader;	//头指针
        skiplist_node<K,V,MAXLEVEL>* m_pTail;	//尾指针
        double uniformRandom();	//随机函数
        int randomLevel();	//生成随机层数

    public:
    	//别名
        typedef K KeyType;
        typedef V ValueType;
        typedef skiplist_node<K,V,MAXLEVEL> NodeType;

        const int max_level;	//最大层数

        skiplist(K minKey,K maxKey);

        virtual ~skiplist_node();

        void insert(K searchKey,V newValue); //插入

        void erase(K searchKey);	//删除

        const NodeType* find(K searchKey);	//查找

        bool empty() const;	//判断是否为空

        std::string printList();	//打印
};

skiplist.cc

#include "stdlib.h"

double skiplist::uniformRandom()	//随机函数
{
    return rand()/double(RAND_MAX);
}

int skiplist::randomLevel()	//生成随机层数
{
    int level=1;
    double p=0.5;
    while(uniformRandom()<p && level < MAXLEVEL)
                level++;
    return level;
}

skiplist::skiplist(K minKey,K maxKey):m_pHeader(NULL),m_pTail(NULL),	//构造函数
                                max_curr_level(1),max_level(MAXLEVEL),
                                m_minKey(minKey),m_maxKey(maxKey)
{
        m_pHeader = new NdoeType(m_minKey);
        m_pTail = new NodeType(m_maxKey);
        for(int i=0;i<MAXLEVEL;i++)
            m_pHeader->forwards[i]=m_pTail;
}

skiplist::~sikplist()	//析构函数
{
        NodeType* currNode =m_pHeader->forwards[0];
        while(currNode != m_pTail)
        {
            NodeType* tempNode = currNode;
            currNode = currNode->forwards[0];
            delete tempNode;      
         }
         delete m_pHeader;
         delete m_pTail;
}

void skiplist::insert(K searchKey,V newValue)	//插入函数
{
        skiplist_node<K,V,MAXLEVEL>* update[MAXKLEVEL];
        NodeType* currNode= m_pHear;
        for(int level=max_curr_level-1; level>=0; level--)  //这个循环用来查找待插入的位置
        {	//若第level指向的小于 待查找(searchKey) 的值,则在本层一直往下走
                while (currNdoe->forwards[level]->key<searchKey)	
                        currNode = currNode->forwards[level];
                //否则,到下一层
                update[level] = currNode;	//存储走过的路径
        }
        currNode = currNode->forwards[0];	//此时便是待插入的位置
        if(currNode->key == searchKey)	//若键值 key 相同
                    currNode->value = newValue;	//更新值
        else	//否则进行插入
        {
            int newlevel = randmoLevel();	//或者随机层数
            if(newlevel > max_curr_level)	//大于当前最大层数,则需要更新
            {
                for(int level = max_curr_level; level < newlevel; level++)
                    upadte[level]=m_pHeader;
                
                max_curr_level=newlevel;
            }
            currNode=new NodeType(searchKey,newValue);	
            //更新指针的指向,比新节点高的则指向updata后面的,比新节点低的则将updata指向新节点
            for(int lv=0;lv<max_curr_level;lv++)	
            {
                    currNode->forwards[lv]=update[lv]->forwards[lv];
                    update[lv]->forwards[lv]=currNode;
            }
        }   
}

void listskip::erase(K searchKey)
{
        skiplist_node<K,V,MAXLEVEL>* update[MAXLEVEL];
        NodeType* currNode = mpHeader;
        for(int level=max_curr_level-1; level>=0; level--)	//同上面的 insert
        {
                while(currNode->forwards[level]->key < searchKey)
                        currNode = currNode->forwards[level];
                
                update[level]=currNode;
        }
        currNode = currNode->forwards[0];	//找到需要查找的位置
        if(currNode->key == searchKey)//如果相等则说明存在需要删除
        {
            for(int lv=0;lv<max_curr_level;lv++)	//更新指针指向
            {
                    if(update[lv]->forwards[lv]!=currNode)  break;
                    update[lv]->forwards[lv]=currNode->forwards[lv];
            }
            delete currNode;    

                // update the max level
            while(max_curr_level > 0  && m_pHeader->forwards[max_curr_level]==nullptr)
                max_curr_level--;
        }
        
        
}

const NodeType* skiplist::find(K searchKey)
{
        NodeType* currNode = m_pHeader;
        for(int level=max_curr_level-1;level>=0;level--)	//同上,但寻找无需存储走过的路径
                while(currNode->forwards[level]->key < searchKey)
                            currNode = currNode->forwards[level];
        currNode = currNode->forwards[0];
        if(currNode->key==searchKey)	//找到返回 找到的节点
                return currNode;
        else return nullptr;	//否则返回空
}

bool skiplist::empty() const	//判断是否为空
{
        return (m_pHeader->forwards[0]==m_pTail);
}

std::string skiplist::printList()	//打印
{
        std::stringstream sstr;
        NodeTpye* currNode = m_pHeader->forwards[0];
        while(currNode != m_pTail)
        {
            sstr << "(" << currNode->key << "," << currNode->value << ")" << endl;
            currNode = currNode->forwards[0];
        }
        return sstr.str();
}

因为代码和上面图解有点差别,所以这里有几点需要说明:
1、代码存储的是<key,value>的形式,因此不单单是上述单个值。key是用来比较的
2、代码的头、尾节点存储的是特定的 min_valuemax_value ,而不是图解中的 NULL,因此key需要有一个特定的范围

0X 30 后言

需要说明一下的是,上面的内容,除了一堆骚话,大多是参考网上大佬的,希望大家不要介意。

最后贴上大佬们的文章链接,大家一起学习呀~

那我们就江湖再见了

跳表──没听过但很犀利的数据结构

C++ Implementation of Skiplist

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值