skiplist

前几天在看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
#include

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_value 和 max_value ,而不是图解中的 NULL,因此key需要有一个特定的范围

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值