github.com/benz9527/toy-box/algo@v0.0.0-20240221120937-66c0c6bd5abd/list/skip_list.md (about)

     1  # SKIP LIST
     2  
     3  ## 引子-有序单向链表
     4  每个元素存放下一个元素的引用,但是要查找某个元素,需要从头开始遍历,时间复杂度为O(n)
     5  
     6  在有序单向链表的基础上,如果能增加索引,那么获取某个元素的时间复杂度就可以降低为
     7  
     8  通常降低为 O(logn) 的复杂度,需要使用二分查找,但是二分查找需要数组,而链表是无法使用二分查找的。
     9  
    10  因为链表不能做到 random access。
    11  
    12  如果我们的索引是每间隔一半的元素而递归地建立,或许我们就可以使用二分查找了。
    13  
    14  按照这个思路,我们或许需要建立多级索引,每级索引的元素间隔为上一级索引的元素的一半。
    15  
    16  ![多级索引结构](./assets/skiplist-multiple-indexes.png)
    17  
    18  一旦能应用上二分查找,那么这种结构在时间复杂度上就等同于红黑树了。
    19  
    20  ## 跳表
    21  
    22  ![跳表](./assets/skiplist.png)
    23  
    24  ### 与 rbtree 的比较
    25  1. 都可以插入、删除、查找,时间复杂度都为 O(logn)
    26  2. 都可以有序遍历
    27  3. 唯一不同的是,跳表可以进行范围查找,但是红黑树可以,但是效率低下
    28  
    29  ### Insert
    30  
    31  ![跳表插入](./assets/skiplist-insert.png)
    32  
    33  ![跳表插入动态](./assets/skiplist.gif)
    34  
    35  每个元素插入时要随机生成level
    36  
    37  最底层的链表里面存放了所有的元素
    38  
    39  如果一个元素出现在第 i 层索引中,那么它一定会出现在 i 以下的层中
    40  
    41  索引层有两个指向,一个指向下一个元素(逻辑上指向),一个指向下一层索引的元素
    42  
    43  ### Remove
    44  
    45  ![跳表删除](./assets/skiplist-remove.png)
    46  
    47  跳表中,每一层索引其实都是一个有序的单链表,单链表删除元素的时间复杂度为 O(1),索引层数为 logn 表示最多需要删除 logn 个元素,所以删除元素的总时间包含 查找元素的时间 加 删除 logn个元素的时间 为 O(logn) + O(logn) = 2 O(logn),忽略常数部分,删除元素的时间复杂度为 O(logn)。
    48  
    49  ## 无锁并发跳表
    50  
    51  ### 无锁删除
    52  
    53  - 铰接点:指的是当前结点的前一个结点
    54  
    55  ### EBR (epoch based reclamation)
    56  基于世代的回收算法。引入经典的读、删并发场景。
    57  
    58  例如链表读取的线程在读取节点N,但N节点已经被另一个并发线程删除,由于存在线程任意顺序执行的组合,读线程可能拿到了一个删除且回收的节点。
    59  
    60  
    61  # References
    62  
    63  https://www.jianshu.com/p/9d8296562806
    64  
    65  https://zhuanlan.zhihu.com/p/600729377
    66  
    67  https://zhuanlan.zhihu.com/p/600961328
    68  
    69  https://github.com/zzy590/article-code/blob/main/concurrent_skip_list.h
    70  
    71  https://github.com/facebook/rocksdb/blob/main/memtable/inlineskiplist.h