9.2 (Skiplist)跳转表 c++实现

参考博客:skiplist 跳转表基本概念
参考博客:跳转表

本章问题

1.什么是跳转表

跳转表是一个基于列表list的数据结构,从结构上来说,它是由多个列表组成的。各个列表在纵向形成多层,其中第一层(最底层)拥有跳转表中的所有数据节点,以上各层列表中的数据都是其底层列表的一个子集,特别地,最顶层的列表不包含任何数据,仅含有两个头尾哨兵。跳转表的结构如图所示:img

跳转表的初衷在于,相对于二叉树更加直观简便。它是一种基于链表的结构,不同之处在于,节点需要包含上下左右四个方向的指针,查询和动态操作仅需要O(logn)的时间。跳转表的总体结构如下图所示。

可以看到,需要用两个链表来构成跳转表结构,其中,每一个水平链表称为一层(level),纵向链表的规模称为层高,从S0-Sh,元素的数量递减,最底层的S0包含有表中所有的数据项;同时,同一个数据项可能在几层都出现,沿纵向组成塔(tower),从而也需要给每一个节点定义上下两个指针。

可以自然地想到,这种结构会浪费一定的空间,因为有许多不必要的重复词条,但这也正是跳转表结构效率的来源,空间换时间。如果每个词条都有很多重复,不仅接近于链表O(n)的效率,更是没有必要的浪费。因此约定,在Sk中出现的节点,也出现在Sk+1中的概率为1/2,也就是说,总体上,每一层节点只有它下一层节点数量的的一半。
为了满足四个方向都有指针的需求,需要对链表进行拓展,水平和竖直方向都可以定义后继和前驱的,称为四联表,下面是四联表的实现,总体与链表思路一致,不过因为跳转表的插入规则,只定义了after-above插入的方式。

2.跳转表中元素排序特点

1.节点的下层节点的值等于上层节点
2.节点左边的值小于右边的值

3.跳转表中如何搜索元素的

实际上,在上面地铁那里已经叙述过跳转表的搜索算法了,即首先坐上层的大站车快速到达距离小站最近的位置,然后换乘底层的小站车。用算法的语言描述,即自上而下,自左而右地搜索,对于每一层的列表,从左至右遍历它的每一个元素,直到发现第一个大于搜索值key的元素,然后后退一步,如果当前节点的key值等于搜索值,则成功返回,否则表示应该在这里下车换乘了。如果当前列表还不是最底层,则切换到下一层的列表,并且重复上面的操作,直到查找成功,或者因为不存在该key值而查找失败。
这里的搜索算法将基于遵循前面列表的约定,即返回总是某一层次中不大于key值节点中的最靠右者。

转表的搜索算法仅需要O(logn)的时间复杂度,对该结论可以定性地来理解。由于生长概率逐层减半,跳转表的期望层数只有h = logn,所以搜索算法中至多进行O(logn)次层间跳转;类比于二叉树,每进行一次层间跳转,就相当于排除了最底层一半的节点,因此搜索过程中,横向跳转次数的期望就等于跳转表的层数O(h),所以所有的层间跳转和横向跳转至多进行O(h) = O(logn)次,每次跳转都只需要常数的时间,即整体的时间复杂度为O(logn)。

下面可以严格地证明横向跳转的次数的期望等于跳转表的层数O(h)。

考虑搜索过程的逆过程,即从底层列表向左向上移动,该移动过程可以描述为:在某一层不断向左移动,直到发现了第一个向上生长的节点,或者直到到达第一个可以换乘的大站。设某一层横向移动的次数为K,有
P ( K = 0 ) = p P(K=0)=p P(K=0)=p
P ( K = 1 ) = ( 1 − p ) p P(K=1)=(1−p)p P(K=1)=(1p)p

P ( K = k ) = ( 1 − p ) P(K=k)=(1−p) P(K=k)=(1p)k−1p,k=1,2,…
即随机变量K服从几何分布,其期望EK = 1,即在每一层的期望移动次数仅为1,这样一次搜索过程中的横向跳转次数不超过h。故得证。基于搜索算法,可以快捷地实现跳转表的插入和删除算法。

2.如何进行查找

查找接口skipSearch(),接受起始层数以及起始节点。get()可以通过调用skipSearch()来获取关键码对应的值。可以自然地想到,高层的四联表中节点少,如果能查找到,可以大大减少时间。所以,查找元素的操作,从最上层开始,如果命中,直接返回;如果未找到目标关键码,返回到不大于目标的节点,并转入下层,继续向后寻找。
因为有前面1/2概率生长的约定,空间复杂度的期望值应当为2n,总体为O(n)。相对于链表,只是增加了一个系数,但是查找时横向和纵向的复杂度,都可以大大降低。具体证明就忽略了(其实只要简单的概率论就可以了),可以证明,跳转表的层数期望E(h)=O(logn),整个查找过程中横向和纵向跳转次数均为O(logn)。相对于链表,牺牲了少量的空间,换取了时间复杂度的大大降低。

3.如何进行插入

为了将一个新的词条插入到跳转表中,首先需要调用一次搜索算法来找到正确的插入位置,得益于搜索算法的语义,返回的位置必然某一层次中是不大于待插入关键码的最右词条,因此无论该关键码是否已经在跳转表中存在,都可以简单地插入到该词条的右侧。

在定位了插入位置后,为了将新的词条插入,首先需要将插入位置指针强制转移到底层,从最底层开始插入,完成实质性的插入操作只需要修改前后词条的指针即可。此后,还需要保证生长概率逐层减半,可以通过一个随机数来实现,如果生成的随机数是奇数(表示插入),则继续在上一层插入,这个过程不断重复,直到生成的随机数,为偶数(表示不继续插入)。

为了将新的词条在上一层插入,需要找到该词条在上一层的前驱或者后继节点。一种策略是遍历上一层的全部节点,直到找到新词条的前驱或者后继,其时间复杂度为O(n),毋庸置疑,这种策略的开销太高了。为了快速地找到新词条的前驱(或者后继),可以沿用搜索过程中的跳转策略,具体说来就是在当前层次继续向前遍历,直到发现第一个向上生长的节点,在这里向上层跳转,就是新词条的前驱节点了。在前面也已经证明过了,这种遍历进行次数的期望仅为O(1),因此插入的时间复杂度,主要是消耗在了search过程中,其总体的时间复杂度也是O(logn)。
需要注意的是,在插入过程中,如果在最顶层的列表中进行了插入,则需要新创建一个为空的顶层列表,实际上,这也是跳转表层次增长的唯一原因。

4.如何进行删除

跳转表的删除策略基本是类似于插入的策略的,而且还要更加简单,因为不需要寻找被删除词条的前驱或者后继节点。具体的算法是,首先调用一次search对被删除的词条进行定位,随后即可自上而下依次对各层次中的该词条进行删除,由于这种删除至多会进行h次,因此删除操作的时间复杂度也仍然是O(h) = O(logn)。
需要注意的是,删除操作中也涉及到某一层次列表的删除。即在一次删除操作以后,如果该层次列表为空,则应该对该层次的列表进行删除。这也是跳转表层次降低的唯一原因。

9.2.1 Entry.h

#ifndef ENTRY_H_INCLUDED
#define ENTRY_H_INCLUDED
template<typename K, typename V> struct Entry
{
	K key;      //关键码
	V value;    //数据项
	//构造函数
	Entry(K k = K(), V v = V()) :key(k), value(v) {}
	Entry(const Entry<K, V>& e) :key(e.key), value(value) {}
	bool operator<(const Entry<K, V>& e);
	bool operator>(const Entry<K, V>& e);
	bool operator==(const Entry<K, V>& e);
	bool operator!=(const Entry<K, V>& e);
};
#endif // ENTRY_H_INCLUDED

9.2.2 Dictionary.h

#ifndef DICTIONARY_H_INCLUDED
#define DICTIONARY_H_INCLUDED

template<typename K, typename V> struct Dictionary
{
	virtual int size() const=0;//当前词条总数
	virtual bool put(K,V)=0;//插入词条
	virtual V*get(K k)=0;//读取词条
	virtual bool remove(K k)=0;//删除词条
};

#endif // DICTIONARY_H_INCLUDED

9.2.3 QuadlistNode.h

#ifndef QUADLISTNODE_H_INCLUDED
#define QUADLISTNODE_H_INCLUDED

#include<iostream>
#include"Entry.h"
#define QlistNodePosi(T) QuadlistNode<T>*
template<typename T>
struct QuadlistNode
{
    T entry;
    QlistNodePosi(T) pred; QlistNodePosi(T) succ;//前驱后继
    QlistNodePosi(T) above; QlistNodePosi(T) below;//上邻下邻
    QuadlistNode(T e = T(), QlistNodePosi(T) p = NULL, QlistNodePosi(T) s = NULL,
        QlistNodePosi(T) a = NULL, QlistNodePosi(T) b = NULL)
        :entry(e), pred(p), succ(s), above(a), below(b) {}
    QlistNodePosi(T) insertAsSuccAbove(T const&e,QlistNodePosi(T) b=NULL);
    //插入新节点,以当前节点为前驱,以节点b为下邻
};
template <typename T> QlistNodePosi(T)//将e作为当前节点的后继,b的上邻插入Quadlist
QuadlistNode<T>::insertAsSuccAbove(T const &e,QlistNodePosi(T) b)//对于对于函数默认值的声明,
{                                                     //只需要在定义的时候,进行声明就行,在实现的时候,不需要再写一遍
    QlistNodePosi(T) x=new QuadlistNode<T>(e,this,succ,NULL,b);//创建新节点
    succ->pred=x;succ=x;//设置水平逆向链接
    if(b) b->above=x;//设置垂直逆向链接
    return x;//返回新节点的位置
}

#endif // QUADLISTNODE_H_INCLUDED

9.2.4 Quadlist.h(四联表)

#ifndef QUADLIST_H_INCLUDED
#define QUADLIST_H_INCLUDED

#include"QuadlistNode.h"
#define QlistNodePosi(T) QuadlistNode<T>*
template<typename T> class Quadlist
{
private:
    int _size;
    QlistNodePosi(T) header;
    QlistNodePosi(T) trailer;
protected:
    void init();
    int clear();
public:
    Quadlist() { init(); }
    ~Quadlist() { clear(); delete header; delete trailer; }
    int size() const { return _size; }
    bool empty() const{ return _size <= 0; }
    QlistNodePosi(T) first() const { return header->succ; }
    QlistNodePosi(T) last() const { return trailer->pred; }
    bool valid(QlistNodePosi(T) p)
    {
        return p && (p != header) && (p != trailer);
    }
    T remove(QlistNodePosi(T) p);
    QlistNodePosi(T) insertAfterAbove(T const& e, QlistNodePosi(T) p, QlistNodePosi(T) b = NULL);
};
template<typename T>
QlistNodePosi(T)
Quadlist<T>::insertAfterAbove(T const& e, QlistNodePosi(T) p, QlistNodePosi(T) b ){
    _size++;
    return p->insertAsSuccAbove(e,b);//返回新节点位置(below)
}
template<typename T> void Quadlist<T>::init()//初始化,构造Quadlist统一调用
{
    header = new QuadlistNode<T>;//创建投哨兵节点
    trailer = new QuadlistNode<T>;//创建尾哨兵节点
    header-> succ = trailer;//沿横向联结哨兵
    header-> pred = NULL;
    trailer->pred = header;//沿横向联结哨兵
    trailer->succ = NULL;
    header-> above = trailer->above = NULL;//纵向的前驱置空
    header-> below = trailer->below = NULL;//纵向的后继置空
    _size = 0;//初始化规模
}//构造的四联表暂时不含实质节点,且与其他四联表暂时独立
template<typename T> T Quadlist<T>::remove(QlistNodePosi(T) p)
{
    p->pred->succ = p->succ; p->succ->pred = p->pred;
    _size--;
    T e = p->entry; delete p;
    return e;
}
template<typename T> int Quadlist<T>::clear()
{
    int oldsize = _size;
    while (_size > 0) remove(header->succ);
    return oldsize;
}

#endif // QUADLIST_H_INCLUDED

9.2.5 跳转表(Skiplist)

#incldue"List.h"
#include"Entry.h"
#include"Quadlist.h"
#include"Dictionary.h"
template<typename K, typename V> class Skiplist :public Dictionary<K, V>, public List<Quadlist<Entry<K, V>>*>
{
protected:
    bool skipSearch(ListNode<Quadlist<Entry<K, V>>*>* &qlist, QuadlistNode<Entry<K, V>>* &p, K& k);
public:
    int size() const { return empty() ? 0 : last()->data->size(); }
    int level() { return List:; size(); }
    bool put(K k, V v);//插入,允许重复故必然成功
    V* get(K k);
    bool remove(K k);
};
9.2.3.1 ADT 接口
操作功能
size( )获取数据规模
level( )返回层数
put(k,v )插入节点
get(K k )查找
remove(K k)删除节点
9.2.3.2 get
template<typename K, typename V> V* Skiplist<K,V>::get(K k)
{
    if (empty()) return NULL;
    ListNode<Quadlist<Entry<K, V>>*>* qlist = first();
    QuadlistNode<Entry<K, V>>* p = qlist->data->first();
    return skipSearch(qlist, p, k) ? &(p->entry.value) : NULL;
}
9.2.3.3 skipSearch
template<typename K, typename V> bool Skiplist<K, V>::skipSearch(ListNode<Quadlist<Entry<K, V>>*>* &qlist,
    QuadlistNode<Entry<K, V>>* &p, K& k)
{
    while (true)
    {
        while (p->succ && (p->entry.key <= k)) p = p->succ;
        p = p->pred;//回撤一步
        if (p->pred && (p->entry.key == k)) return true;
        qlist = qlist->succ;
        if (!qlist->succ) return false;//已经是链表的trailer,失败
        p = (p->pred) ? p->below : qlist->data->first();//转到下一层(p已经是头哨兵需要转到下一层的头哨兵)
    }
}
9.2.3.4 put
template<typename K, typename V> bool Skiplist<K, V>::put(K k, V v)
{
    Entry<K, V> e = Entry<K, V>(k, v);
    if (empty()) insertAsFirst(new Quadlist<Entry<K, V>>);//插入首个Entry(首层)
    ListNode<Quadlist<Entry<K, V>>*>* qlist = first();
    QuadlistNode<Entry<K, V>>* p = qlist->data->first();
    if (skipSearch(qlist, p, k))
        while (p->below) p = p->below;
    qlist = last();
    QuadlistNode<Entry<K, V>>* b = qlist->data->insertAfterAbove(e, p);//在最底层上插入新的基座
    while (rand() & 1)
    {
        while (qlist->data->valid(p) && !p->above) p = p->pred;//找到第一个比其高的前驱
        if (!qlist->data->valid(p))
        {
            if (qlist == first())//需要升层而已经是最高层时
                insertAsFirst(new Quadlist<Entry<K, V>>);//新加一层
            p = qlist->pred->data->first()->pred;//转至新加层的header
        }
        else
            p = p->above;
        qlist = qlist->pred;//升层
        b = qlist->data->insertAfterAbove(e, p, b);
    }
    return true;
}
9.2.3.5 remove
template<typename K, typename V> bool Skiplist<K, V>::remove(K k)
{
    if (empty()) return false;
    ListNode<Quadlist<Entry<K, V>>*>* qlist = first();
    QuadlistNode<Entry<K, V>>* p = qlist->data->first();
    if (!skipSearch(qlist, p, k)) return false;
    do
    {
        QuadlistNode<Entry<K, V>>* lower = p->below;
        qlist->data->remove(p);
        p = lower; qlist = qlist->succ;//记录,向下深入删除
    } while (qlist->succ);
    while (!empty() && first()->data->empty())//如果Quadlist为空,删除
        List::remove(first());
    return true;
}

9.2.6 Skiplist.h

#ifndef SKIPLIST_H_INCLUDED
#define SKIPLIST_H_INCLUDED

#include"List.h"
#include"Quadlist.h"
#include"Dictionary.h"
template<typename K, typename V> class Skiplist :public Dictionary<K, V>, public List<Quadlist<Entry<K, V>>*>
{
protected:
    bool skipSearch(ListNode<Quadlist<Entry<K, V>>*>* &qlist, QuadlistNode<Entry<K, V>>* &p, K& k);
public:
    int size() const { return this->empty() ? 0 : this->last()->data->size(); }//底层Qualist的规模
    int level() { return List<Quadlist<Entry<K, V>>>::size(); }//层高
    bool put(K k, V v);//插入,允许重复故必然成功
    V* get(K k);//读取
    bool remove(K k);//删除
};
template<typename K, typename V> V* Skiplist<K,V>::get(K k)
{
    if (List<Quadlist<Entry<K, V>>*>::empty()) return NULL;//表为空,就返回
    ListNode<Quadlist<Entry<K, V>>*>* qlist = this->first();//从顶层Quadlsit的
    QuadlistNode<Entry<K, V>>* p = qlist->data->first();//从首节点开始

    return skipSearch(qlist, p, k) ? &(p->entry.value) : NULL;//查找并报告
    //注意这里可能导致错误,三目运算符可能返回值异常,因为左右类型会自动向上兼容
}//有多个命中时,靠后者优先
//入口
template<typename K, typename V>
bool Skiplist<K, V>::skipSearch(ListNode<Quadlist<Entry<K, V>>*>* &qlist,QuadlistNode<Entry<K, V>>* &p, K& k)
{
    while (true)
    {
        while (p->succ && (p->entry.key <= k)) p = p->succ;
        p = p->pred;//回撤一步
        if (p->pred && (p->entry.key == k)) return true;
        qlist = qlist->succ;
        if (!qlist->succ) return false;//已经是链表的trailer,失败
        p = (p->pred) ? p->below : qlist->data->first();//转到下一层(p已经是头哨兵需要转到下一层的头哨兵)
    }
}
template<typename K, typename V> bool Skiplist<K, V>::put(K k, V v)
{
    Entry<K, V> e = Entry<K, V>(k, v);
    if (this->empty()) this->insertAsFirst(new Quadlist<Entry<K, V>>);//插入首个Entry(首层)
    ListNode<Quadlist<Entry<K, V>>*>* qlist = this->first();
    QuadlistNode<Entry<K, V>>* p = qlist->data->first();
    if (skipSearch(qlist, p, k))
        while (p->below) p = p->below;
    qlist = this->last();
    QuadlistNode<Entry<K, V>>* b = qlist->data->insertAfterAbove(e, p);//在最底层上插入新的基座
    while (rand() & 1)
    {
        while (qlist->data->valid(p) && !p->above) p = p->pred;//找到第一个比其高的前驱
        if (!qlist->data->valid(p))
        {
            if (qlist == this->first())//需要升层而已经是最高层时
                this->insertAsFirst(new Quadlist<Entry<K, V>>);//新加一层
            p = qlist->pred->data->first()->pred;//转至新加层的header
        }
        else
            p = p->above;
        qlist = qlist->pred;//升层
        b = qlist->data->insertAfterAbove(e, p, b);
    }
    return true;
}
template<typename K, typename V> bool Skiplist<K, V>::remove(K k)
{
    if (this->empty()) return false;
    ListNode<Quadlist<Entry<K, V>>*>* qlist = this->first();
    QuadlistNode<Entry<K, V>>* p = qlist->data->first();
    if (!skipSearch(qlist, p, k)) return false;
    do
    {
        QuadlistNode<Entry<K, V>>* lower = p->below;
        qlist->data->remove(p);
        p = lower; qlist = qlist->succ;//记录,向下深入删除
    } while (qlist->succ);
    while (!this->empty() && this->first()->data->empty())//如果Quadlist为空,删除
        List<Quadlist<Entry<K, V>>*>::remove(this->first());//清除已可能不含词条的顶层Quadlsit
    return true;
}


#endif // SKIPLIST_H_INCLUDED

9.2.7 Skilplist.h的测试

#include"Skiplist.h"
#include<iostream>
using namespace std;
int main(){
    Skiplist<int, char> t;
    Entry<int, char> ks=Entry<int, char>(3,'c');
    t.put(3,'c');
    if(t.get(3)) cout<<"right"<<endl;//right
    t.remove(3);
    if(!t.get(3)) cout<<"right"<<endl;//right
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值