skip list(跳表)

第一次看到这种数据结构还是刚接触ocean base架构的时候。粗略扫了几眼,以为是一个简单的二级索引,没有仔细考虑就略过了。后来去北京出差,经神夜路点播,遂明白这种链表式结构的简约而不简单,有一种四两拨千斤的优雅。

Skip lists are a data structure that can be used in place of balanced trees. Skip lists use probabilistic balancing rather than strictly enforced balancing and as a result the algorithms for insertion and deletion in skip lists are much simpler and significantly faster than equivalent algorithms for balanced trees.
--William Pugh

相比于红黑树,B树,AVL树,跳表的实现相当简单,同时,由于其多维链表的特性,使得跳表可以支持无锁的多读一写。(链表的多读一写无锁实现方式这里就不展开了)。
不同于B树,跳表的平衡性依靠随机算法,在正常情况下,该结构的查找,插入,删除的时间复杂度都是logN。
先从一维链表开始,我们知道在链表中查找一个元素I的话,需要将整个链表遍历一次。



如果是说链表是排序的,并且节点中还存储了指向前面第二个节点的指针的话,那么在查找一个节点时,仅仅需要遍历N/2个节点即可。



这基本上就是跳表的核心思想,其实也是一种通过“空间来换取时间”的一个算法。
下面我们来看一个4层跳表示例:

查找时首先从高层开始查找,之后逐渐降低层次靠近数据,完成定位。

插入操作:



由于跳表数据结构整体上是有序的,所以在插入时,需要首先查找到合适的位置,然后就是修改指针(和链表中操作类似),然后更新跳表的level变量。
补充一个数据节点层次确定算法:
int height = 1;
    
while (height < kMaxHeight && ((rnd_.Next() % kBranching) == 0)) 
{
     height++;
}

可以发现层级越高的节点越少,因此跳表整体的指针开销并不高。 相比于同级别的树形实现,跳表具有更快的速度,更低的空间开销和更简单的实现。

/*
·* skipList.h
·*
·* ·Created on: 2013年8月7日
·* · · ·Author: sigh.xy
·*/
#ifndef SKIPLIST_H_
#define SKIPLIST_H_
#include <iostream>
#include <stack>
//rand
#include<stdlib.h>
template <class Key = int, class Value = int>
class Node
{
public:
    Key key;
    Value value;
    Node(Key k, Value v) : key(k), value(v){}
    Node(){}
};
template <class Key = int, class Value = int>
class Element
{
    Node<Key, Value> node;
    Element** next;
public:
    Element() : next(NULL) {}
    Element(Node<Key, Value> node, int level)
    {
        this->node = node;
        next = new Element*[level];
        for (int i = 0; i < level; i++)
        {
            next[i] = NULL;
        }
    }
    void setNext(int place, Element* nElement)
    {
        next[place] = nElement;
    }
    Element* & getNext(int place)
    {
        return next[place];
    }
    Key getKey()
    {
        return node.key;
    }
    Value getValue()
    {
        return node.value;
    }
    ~Element()
    {
        if (next)
        {
            delete[] next;
        }
    }
};
//declare
template <class Key, class Value>
class SkipIterator;
template <class Key = int, class Value = int, int MAXLEVEL = 4>
class SkipList
{
    //head
    Element<Key, Value>** head;
    int randLevel(int level = MAXLEVEL);
    void findWay(Key key, std::stack<Element<Key, Value>** >& pStack);
public:
    typedef SkipIterator<Key, Value> Iterator;
    SkipList()
    {
        head = new Element<Key, Value>*[MAXLEVEL];
        for (int i = 0; i < MAXLEVEL; i++)
        {
            head[i] = NULL;
        }
    }
    Value find(Key key);
    bool insert(Key key, Value value);
    Iterator begin()
    {
        return head[0];
    }
    Iterator end()
    {
        return Iterator();
    }
    //another kind of insert
    //bool delKey(Key key);
    ~SkipList()
    {
        Element<Key, Value>* cur = head[0];
        while (cur)
        {
            Element<Key, Value>* tmp = cur;
            cur = cur->getNext(0);
            delete tmp;
        }
    }
};
//查找数据
template <class Key, class Value, int MAXLEVEL>
Value SkipList<Key, Value, MAXLEVEL>::find(const Key key)
{
    if (NULL == head)
    {
        return (Value) 0;
    }
    //std::cout << "ok" << std::endl;
    int rawL = MAXLEVEL - 1;
    Element<Key, Value>* cur = NULL;
    //find the first < place
    while (rawL >= 0)
    {
        if (head[rawL] && head[rawL]->getKey() == key)
        {
            return head[rawL]->getValue();
        }
        else if (head[rawL] && head[rawL]->getKey() < key)
        {
            cur = head[rawL];
            break;
        }
        rawL--;
    }
    //std::cout << "rawL = " << rawL << std::endl;
    while (rawL >= 0)
    {
        if (cur && cur->getKey() == key)
        {
            return cur->getValue();
        }
        else if (cur->getNext(rawL) && cur->getNext(rawL)->getKey() <= key)
        {
            cur = cur->getNext(rawL);
        }
        else
        {
            rawL--;
        }
    }
    return (Value) 0;
}
//通过栈记录查找路径,用于插入操作。
template <class Key, class Value, int MAXLEVEL>
void SkipList<Key, Value, MAXLEVEL>::findWay(Key key,
        std::stack<Element<Key, Value>** >& pStack)
{
    int rawL = MAXLEVEL - 1;
    Element<Key, Value>* cur = NULL;
    //find the first < place
    while (rawL >= 0)
    {
        if (head[rawL] && head[rawL]->getKey() < key)
        {
            cur = head[rawL];
            break;
        }
        pStack.push(&head[rawL]);
        rawL--;
    }
    while (rawL >= 0)
    {
        if (cur->getNext(rawL) && cur->getNext(rawL)->getKey() <= key)
        {
            cur = cur->getNext(rawL);
        }
        else
        {
            pStack.push(&cur->getNext(rawL));
            rawL--;
        }
    }
}
//插入操作
template <class Key, class Value, int MAXLEVEL>
bool SkipList<Key, Value, MAXLEVEL>::insert(Key key, Value value)
{
    int level = randLevel();
    Element<Key, Value>* element =
            new Element<Key, Value>(Node<Key, Value>(key, value), level);
    std::stack<Element<Key, Value>**> pStack;
    findWay(key, pStack);
    for (int i = 0; i < level; i++)
    {
        element->getNext(i) = *pStack.top();
        *(pStack.top()) = element;
        //std::cout << "head = " << head[0]->getValue() << std::endl;
        pStack.pop();
    }
    //std::cout << head[0]->getNext(0) << std::endl;
    return true;
}
template <class Key, class Value, int MAXLEVEL>
int SkipList<Key, Value, MAXLEVEL>::randLevel(int level)
{
    int height = 1;
    while (height < MAXLEVEL && ((rand() % level) == 0))
    {
        height++;
    }
    return height;
}
//iterator
template <class Key, class Value>
class SkipIterator
{
private:
    Element<Key, Value>* element;
public:
    typedef Element<Key, Value> EType;
    SkipIterator(Element<Key, Value>* e) : element(e) {}
    SkipIterator()
    {
        element = NULL;
    }
    EType& operator*()
    {
        return *element;
    }
    void operator++()
    {
        element = element->getNext(0);
    }
    void operator++(int)
    {
        ++*this;
    }
    bool operator!= (SkipIterator<Key, Value> right)
    {
        return this->element != right.element;
    }
};
#endif /* SKIPLIST_H_ */

基于模版的某种实现方式没有做很严格的测试,因此不保证正确性。
(原文时间2013-8-5)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容