美文网首页
跳跃表(skiplist)

跳跃表(skiplist)

作者: 小幸运Q | 来源:发表于2019-01-04 19:41 被阅读45次

  • 鸣谢:

https://blog.csdn.net/xiaolei1982/article/details/50721877

https://zhuanlan.zhihu.com/p/53975333?utm_source=qq&utm_medium=social&utm_oi=636829908081446912


用空间换时间:

对于一个需要频繁插入删除的线性有序结构,如何使插入/删除的速度提升?

  • 对于单向链表,无法使用二分查找无法降低复杂度,只能从头到脚遍历O(n)
  • 对于数组,删除插入复杂度太高,O(n)
  • 其实平衡二叉树挺好的,但是节点平衡操作的开销很大,如果插入删除频繁性能会受限。
  • 上红黑树的话,性能差不多,但是如果需要多进程同时访问修改的话,红黑树有个平衡的过程,牵涉到大量的节点,争锁的代价也就相对较高了。

跳表:时间复杂度 O(log2(n))

加入多层索引加快访问速度:

image.png

如果有n个元素,因为是2分,所以层数就应该是上面的log2(n)层再加上自身的1层。

插入过程:

  • 插入元素时生成一个随机数(0,1)决定是否插入第一层索引:

  • 如果是1,则插入第一层索引,否则不插;随后不断重复该操作,直到第N层索引才停止:

注意:插入过程需要利用上层索引找到插入位置。
image.png

删除过程:

将该元素及其位于上层的相同元素顺蔓摸瓜全部删除。(具体操作是利用索引指针圈定范围然后由上而下逐个删除)


image.png

总结一下步骤:

  • 插入:
  1. 通过索引指针圈定插入元素所在的范围,最后确定底层链表的插入位置。O(logN)
  2. 利用抛硬币的随机方式,决定新节点是否提升为上一级索引。结果为“正”则提升并继续抛硬币,结果为“负”则停止。O(logN)
  3. 注意:索引还有最底层的操作要分开,因为索引需要递归下降,而最底层不需要,删除的时候也是。
  • 删除:
  1. 自上而下,查找第一次出现节点的索引,并逐层找到每一层对应的节点。O(logN)
  2. 删除每一层查找到的节点,通过插入时在头结点做的标记,如果删完后该层只剩下1个节点,则删除一整层(原链表除外)。O(logN)

如果出现重复的元素要插入的话怎么办?

上层索引不需要变,但是最底层数据层节点的*underline可以用于存储拥有相同元素值的数据链。当然为了偷懒我在代码里面没写有关数据重复的处理。


示例代码:
  • 要注意删除还有插入的时候最底层节点不需要下沉

  • v.size()会变,不能放在删除处理的循环里面。

#include<iostream>
#include<vector>
#include<string>
#include<cstring>
#include<map>
#include <stdlib.h>
#include <time.h>
using namespace std;
#define N 1000
// 使用带头结点的链表,本示例中不考虑重复元素插入删除的情况。
typedef struct Node{
  int num;
  // 每层的首元素对应的num用于计算行的长度,其他层用于存放数据
  Node*next;
  Node*underline;
  Node(){
    next=NULL;
    num=0;
    underline=NULL;
  }
}Node;
// 按照从小到大插入一行。确定插入位置需要上层索引指引。
// 刚好命中上层元素(m<num<n),
// 沿着m从上到下逐个收缩(每次需要两个指针一前一后这样好回退)
Node* addline(vector<Node*>&v,int line,int num){
  int i=0;
  v[line]->num++;
  // 该行的长度++
  Node*t=new Node();
  t->num=num;

  int linenow=v.size()-1;
  // 当前所在行

  // 初始化两个delete指针
  Node*p=v[linenow];
  Node*q=p->next;
  // 自顶向下到对应 line的上一层节点
  for(;linenow>line;linenow--){
      while(q!=NULL){
        if(q->num>num){
          break;
        }
        q=q->next;
        p=p->next;
      }
      p=p->underline;
      q=p->next;
    }
    // line层不需要下沉(p=p->underline)
    while(q!=NULL){
      if(q->num>num){
        break;
      }
      q=q->next;
      p=p->next;
    }
    // 将点插入p,q两个指针之中
    t->next=q;
    p->next=t;
    return t;
}
// 添加插入的元素到最底层,然后再从下往上逐层按概率添加。
void add(vector<Node*>&v,int num){
  // topline指向上一层插入的num节点,nextline指向下一层插入的num节点,方便上下连接。
  Node* topline;
  Node* underline;
  // 最底层必加,但是最底层不是索引不需要随机数判断。
  underline=addline(v,0,num);
  int height=1;
  // 标记插入的行数

  // srand必须放在函数外面,最好放在main函数里面。
  while(rand()%2==1){
    if(height<v.size()){
      topline=addline(v,height,num);
      topline->underline=underline;
      underline=topline;
    }
    else{
      // 超过表长,添加头节点
      Node* t=new Node();
      t->underline=v[v.size()-1];
      v.push_back(t);
      // 添加元素
      Node *node=addline(v,height,num);
      topline=node;
      topline->underline=underline;
      underline=topline;
    }
    height++;
  }
}
// 依层打印
void print(vector<Node*>v){
  int i;
  cout<<"v has level: "<<v.size()-1<<endl;
  for(i=0;i<v.size();i++){
    cout<<"No."<<i<<" level:  ";
    Node*p=v[i]->next;
    while(p!=NULL){
      cout<<p->num<<" ";
      p=p->next;
    }
    cout<<endl;
  }
}
// 删除用它的underline指针
void del(vector<Node*>&v,int num){
  int i,j;
  int linenow=v.size()-1;
  Node*p;
  Node*q;
  while(linenow>=0){
    p=v[linenow];
    q=p->next;

    while(q!=NULL){
      if(q->num==num){
        // 元素数小于等于2了整行删除
        if(v[linenow]->num<=2){
          p=p->underline;
          q=p->next;
          int sz=v.size();
          // v.size()在pop的时候会变化,所以要先缓存着
          for(i=0;i<sz-linenow;i++)
          {
              // 如果上面有小于1的顶,那么也得一块删除。要不然只删除中间行的操作会很繁琐。
              v.pop_back();
          }
          break;
        }
        // 正常删除
        p->next=q->next;
        free(q);

        // 如果不是最底层不需要下沉
        if(linenow>0){
          p=p->underline;
          q=p->next;
        }
        break;
      }
      else if(q->num>num){
        p=p->underline;
        q=p->next;
        break;
      }
      else{
        p=p->next;
        q=q->next;
      }
    }
    linenow--;
  }
}
int main(){
  int i,j;
  vector<Node*>v;
  Node *node=new Node();
  v.push_back(node);
  srand((unsigned)time(NULL));
  add(v,4);
  add(v,2);
  add(v,3);
  add(v,1);
  add(v,6);
  add(v,5);
  print(v);
  // 输入要删除的元素
  cin>>i;
  del(v,i);
  print(v);
}
image.png

相关文章

  • 跳跃表(skiplist)

    鸣谢: https://blog.csdn.net/xiaolei1982/article/details/507...

  • SkipList(跳跃表)

    简介   跳跃表是一种单链表形式的链式结构,不同于一般的链式结构其为多层链式结构。正因为这种多层结构从而相比于单式...

  • skiplist 跳跃表分析

    跳表(skip List)是一种随机化的数据结构,基于并联的链表,实现简单,插入、删除、查找的复杂度均为O(log...

  • 科普跳跃表-SkipList

    什么是跳表 跳表是一种有序的数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问的目的。 核心...

  • 跳跃表skiplist原理

    github地址:https://github.com/arkulo56/thought/blob/master/...

  • Redis 跳跃表(skiplist)

    Redis基础类型中的有序集合、集群节点的内部数据结构用到了跳跃表(skiplist)。 5.1 跳跃表的实现 图...

  • Redis(2)——跳跃表

    一、跳跃表简介 跳跃表(skiplist)是一种随机化的数据结构,由 William Pugh 在论文《Skip ...

  • python实现跳跃表(SkipList)

    跳跃表是一种随机化的数据结构,目前开源软件 Redis 和 LevelDB 都有用到它,它的效率和红黑树以及 AV...

  • Redis设计与实现4 有序集合对象(跳跃表)的介绍

    跳跃表  跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速...

  • Redis-数据结构-跳跃表

    跳跃表(skiplist) 跳跃表是一种有序数据结构,通过在每个节点中维护多个指向其他节点的指针,达到快速访问节点...

网友评论

      本文标题:跳跃表(skiplist)

      本文链接:https://www.haomeiwen.com/subject/iascrqtx.html