跳表学习笔记,简单分析和引申

跳表学习笔记,简单分析和引申_第1张图片

当我们要去查询一个单链表的时候,如果要找某个节点,那么需要遍历整个链表。时间复杂度是 O ( n ) O(n) O(n)。鱼是我们可以优化链表的数据结构,不去一个个的遍历。而是加上索引去遍历索引。

跳表学习笔记,简单分析和引申_第2张图片

如果要更加快的搜索想找的节点,可以加第二级索引。

跳表学习笔记,简单分析和引申_第3张图片

当元素很多时,为了优化查询效率我们可以设置很多级索引,在Redis中跳表的实现中,将多级索引称为层数。Redis中默认的最大层数为32层,当有 2 64 2^{64} 264个元素时才会达到32层。而单纯看查找最有效率的跳表其实就是可以实现二分查找的有序链表(说单纯看查找效率是因为如果考虑插入的时间复杂度,那么二分的方法进行索引设置就不那么快了)。

空间复杂度为 O ( n ) O(n) O(n)

查询

因为索引有 O ( l o g 2 n ) O(log_2n) O(log2n)层,所以很明显跳表的查询时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n)

插入

跳表学习笔记,简单分析和引申_第4张图片

当插入一个结点时,我们还需要维护索引,如果我们全部重建索引,那么每次插入都会使时间复杂度为 O ( n ) O(n) O(n)。Redis中采取的方法是,不去均匀的构建索引分布,而是在插入每个结点的同时,随机给这个结点赋予层数的属性,即它具有多少级索引。每个结点在插入时都有1/2概率为一级索引,1/4概率为二级索引以此类推。

随机选 n/2 个元素做为一级索引、随机选 n/4 个元素做为二级索引、随机选 n/8 个元素做为三级索引,依次类推,一直到最顶层索引。

跳表学习笔记,简单分析和引申_第5张图片

每个索引都有1/2升级为下一级索引。

// 该 randomLevel 方法会随机生成 1~MAX_LEVEL 之间的数,且 :
//        1/2 的概率返回 1
//        1/4 的概率返回 2
//        1/8 的概率返回 3 以此类推
private int randomLevel() {
  int level = 1;
  float SKIPLIST_P = 0.5;
  // 当 level < MAX_LEVEL,且随机数小于设定的晋升概率时,level + 1
  while (Math.random() < SKIPLIST_P && level < MAX_LEVEL)
    level += 1;
  return level;
}

这样插入的时间复杂度是每个层都插入索引的情况为 O ( l o g n ) O(logn) O(logn)

删除

跳表删除结点,需要把索引也删除。因此很明显删除的时间复杂度为 O ( l o g n ) ( 找 到 结 点 ) + O ( l o g n ) ( 一 共 l o g n 个 索 引 ) = 2 O ( l o g n ) O(logn)(找到结点)+O(logn)(一共logn 个索引)=2O(logn) O(logn)+O(logn)logn=2O(logn)。忽略

Redis使用跳表而不使用红黑树进行查询的原因

  1. 快表原理和实现都比红黑树直观和简单

  2. 在查询,删除,插入和有序输出所有元素上,红黑树和跳表的时间复杂度一样,但是跳表可以更好支持按照范围查找元素。

总结

  1. 跳表是实现二分查找的链表。

  2. 插入结点时跳表会随机赋予level(层数)。

  3. 最底层包含所有结点。

  4. level(x)包含所有level(x+1)的结点。

  5. Redis每层指向每层的下一个结点。

  6. 跳表的插入,查询,删除的时间复杂度与平衡二叉树相近。

你可能感兴趣的:(数据结构,链表,redis,java)