skiplist数据结构简介

skiplist数据结构简介

skiplist本质上也是一种查找结构,用于解决算法中的查找问题(Searching),即根据给定的key,快速查到它所在的位置(或者对应的value)。

一般查找问题的解法分为两个大类:一个是基于各种平衡树,一个是基于哈希表。但skiplist却比较特殊,它没法归属到这两大类里面。

skiplist,顾名思义,首先它是一个list。实际上,它是在有序链表的基础上发展起来的。

1.有序列表

我们先来看一个有序链表,如下图(最左侧的灰色节点表示一个空的头结点): 有序列表0

在这样一个链表中,如果我们要查找某个数据,那么需要从头开始逐个进行比较,直到找到包含数据的那个节点,或者找到第一个比给定数据大的节点为止(没找到)。也就是说,时间复杂度为O(n)。同样,当我们要插入新数据的时候,也要经历同样的查找过程,从而确定插入位置。

  • 假如我们每相邻两个节点增加一个指针,让指针指向下下个节点,如下图: 有序列表1

  • 这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半(上图中是7, 19, 26)。现在当我们想查找数据的时候,可以先沿着这个新链表进行查找。当碰到比待查数据大的节点时,再回到原来的链表中进行查找。比如,我们想查找23,查找的路径是沿着下图中标红的指针所指向的方向进行的: 有序列表3

    	23首先和7比较,再和19比较,比它们都大,继续向后比较。
    
    	但23和26比较的时候,比26要小,因此回到下面的链表(原链表),与22比较。
    
    	23比22要大,沿下面的指针继续向后和26比较。23比26小,说明待查数据23在原链表中不存在,而且它的插入位置应该在22和26之间。
    
  • 在这个查找过程中,由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了。需要比较的节点数大概只有原来的一半。利用同样的方式,我们可以在上层新产生的链表上,继续为每相邻的两个节点增加一个指针,从而产生第三层链表。如下图: 有序列表4

  • 在这个新的三层链表结构上,如果我们还是查找23,那么沿着最上层链表首先要比较的是19,发现23比19大,接下来我们就知道只需要到19的后面去继续查找,从而一下子跳过了19前面的所有节点。

可以想象,当链表足够长的时候,这种多层链表的查找方式能让我们跳过很多下层节点,大大加快查找的速度。

skiplist正是受这种多层链表的想法的启发而设计出来的。实际上,按照上面生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似于一个二分查找,使得查找的时间复杂度可以降低到O(log n)。但是,这种方法在插入数据的时候有很大的问题。新插入一个节点之后,就会打乱上下相邻两层链表上节点个数严格的2:1的对应关系。如果要维持这种对应关系,就必须把新插入的节点后面的所有节点(也包括新插入的节点)重新进行调整,这会让时间复杂度重新蜕化成O(n)。删除数据也有同样的问题。

2.skiplist 跳跃列表

skiplist为了避免这一问题,它不要求上下相邻两层链表之间的节点个数有严格的对应关系,而是为每个节点随机出一个层数(level)。比如,一个节点随机出的层数是3,那么就把它链入到第1层到第3层这三层链表中。为了表达清楚,下图展示了如何通过一步步的插入操作从而形成一个skiplist的过程: 跳跃列表

从上面skiplist的创建和插入过程可以看出,每一个节点的层数(level)是随机出来的,而且新插入一个节点不会影响其它节点的层数。因此,插入操作只需要修改插入节点前后的指针,而不需要对很多节点都进行调整。这就降低了插入操作的复杂度。实际上,这是skiplist的一个很重要的特性,这让它在插入性能上明显优于平衡树的方案。这在后面我们还会提到。

根据上图中的skiplist结构,我们很容易理解这种数据结构的名字的由来。skiplist,翻译成中文,可以翻译成“跳表”或“跳跃表”,指的就是除了最下面第1层链表之外,它会产生若干层稀疏的链表,这些链表里面的指针故意跳过了一些节点(而且越高层的链表跳过的节点越多)。这就使得我们在查找数据的时候能够先在高层的链表中进行查找,然后逐层降低,最终降到第1层链表来精确地确定数据位置。在这个过程中,我们跳过了一些节点,从而也就加快了查找速度。

刚刚创建的这个skiplist总共包含4层链表,现在假设我们在它里面依然查找23,下图给出了查找路径: 跳跃列表1

需要注意的是,前面演示的各个节点的插入过程,实际上在插入之前也要先经历一个类似的查找过程,在确定插入位置后,再完成插入操作。

至此,skiplist的查找和插入操作,我们已经很清楚了。而删除操作与插入操作类似,我们也很容易想象出来。这些操作我们也应该能很容易地用代码实现出来。

当然,实际应用中的skiplist每个节点应该包含key和value两部分。前面的描述中我们没有具体区分key和value,但实际上列表中是按照key进行排序的,查找过程也是根据key在比较。

转载于:https://my.oschina.net/u/3863046/blog/2222592

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您介绍一下使用C++设计skiplist数据结构的基本思路和实现方法。Skiplist是一种基于链表的数据结构,支持高效的查找、插入和删除操作,其核心思想是通过在链表中添加多级索引(即跳表),以实现快速查找的目的。 以下是一个简单的skiplist的C++实现代码: ```c++ #include <iostream> #include <cstdlib> #include <ctime> using namespace std; const int MAX_LEVEL = 16; // 定义skiplist的最大层数 // 定义节点结构体 struct Node { int value; Node **forward; // 存储每层索引的指针 Node(int level, int value) { this->value = value; forward = new Node*[level+1]; memset(forward, 0, sizeof(Node*)*(level+1)); } ~Node() { delete[] forward; } }; // 定义skiplist类 class SkipList { public: SkipList(); ~SkipList(); void insert(int value); void remove(int value); bool contains(int value); void print(); private: int level; Node *header; double probability() { return (double) rand() / RAND_MAX; } int randomLevel() { int lvl = 0; while (probability() < 0.5 && lvl < MAX_LEVEL) { lvl++; } return lvl; } }; // 初始化skiplist SkipList::SkipList() { level = 0; header = new Node(MAX_LEVEL, 0); srand(time(NULL)); } // 销毁skiplist SkipList::~SkipList() { if (header != NULL) { delete header; } } // 插入节点 void SkipList::insert(int value) { Node *update[MAX_LEVEL+1]; memset(update, 0, sizeof(Node*)*(MAX_LEVEL+1)); Node *x = header; for (int i = level; i >= 0; i--) { while (x->forward[i] != NULL && x->forward[i]->value < value) { x = x->forward[i]; } update[i] = x; } x = x->forward[0]; if (x == NULL || x->value != value) { int lvl = randomLevel(); if (lvl > level) { for (int i = level+1; i <= lvl; i++) { update[i] = header; } level = lvl; } x = new Node(lvl, value); for (int i = 0; i <= lvl; i++) { x->forward[i] = update[i]->forward[i]; update[i]->forward[i] = x; } } } // 删除节点 void SkipList::remove(int value) { Node *update[MAX_LEVEL+1]; memset(update, 0, sizeof(Node*)*(MAX_LEVEL+1)); Node *x = header; for (int i = level; i >= 0; i--) { while (x->forward[i] != NULL && x->forward[i]->value < value) { x = x->forward[i]; } update[i] = x; } x = x->forward[0]; if (x->value == value) { for (int i = 0; i <= level; i++) { if (update[i]->forward[i] != x) { break; } update[i]->forward[i] = x->forward[i]; } delete x; while (level > 0 && header->forward[level] == NULL) { level--; } } } // 查找节点 bool SkipList::contains(int value) { Node *x = header; for (int i = level; i >= 0; i--) { while (x->forward[i] != NULL && x->forward[i]->value < value) { x = x->forward[i]; } } x = x->forward[0]; return x != NULL && x->value == value; } // 打印skiplist void SkipList::print() { for (int i = 0; i <= level; i++) { Node *x = header->forward[i]; cout << "Level " << i << ": "; while (x != NULL) { cout << x->value << " "; x = x->forward[i]; } cout << endl; } } int main() { SkipList sl; sl.insert(1); sl.insert(2); sl.insert(3); sl.insert(5); sl.insert(4); sl.print(); sl.remove(3); sl.print(); cout << sl.contains(3) << endl; cout << sl.contains(5) << endl; return 0; } ``` 这里我们定义了一个Node结构体,用于存储skiplist中的节点信息,其中包括节点的值和每层索引的指针。然后我们定义了一个SkipList类,实现了插入、删除、查找和打印等操作。在插入操作中,我们使用随机数生成每个节点的层数,以实现跳表的效果;在删除操作中,我们首先查找到要删除的节点,然后按照相应的层数更新每层索引的指针,最后删除该节点即可。在查找操作中,我们从最高层逐层向下查找,直到找到目标节点或者遍历完整个skiplist。 希望这个简单的skiplist C++实现代码可以帮助您更好地理解和使用该数据结构

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值