# 一、介绍 属于单链表的变体,解决了单链表查找慢的问题(单链表需要从头遍历并且无法使用二分查找因为没有指针或下标能找到中间位置) 跳跃列表的查找和插入都是O(logn) # 二、原理 ## 2.1 创建 由于链表无指针的特性导致不能实现快速查找,此时需要改进为跳跃列表,首先在头结点加入一个哨兵 ![[Snipaste_2023-02-23_10-00-41 8.png]] 加入哨兵并把当前作为L0层 ![[Snipaste_2023-02-23_10-00-41 9.png]] 开始构建L1层,从头遍历每个元素,每个元素有50%的概率上升到L1层,如图所示结点2、10、11、13、20、26上升为L1层元素,**同时保留在L0层的位置** ![[Snipaste_2023-02-23_10-00-41 10.png]] 构建L2层,从头遍历L1层元素,过程与L0到L1相同,如图所示2、13、26从L1上升到L2层,**同时保留在L1层的位置**,根据概率论可以算出元素2来到L2层的概率为25%,50%(L1)*50%(L2)=25% ![[Snipaste_2023-02-23_10-00-41 11.png]] 构建L3层,从头遍历L2层元素,过程与L1到L2相同 ![[Snipaste_2023-02-23_10-00-41 12.png]] 跳跃的意义在于可以快速到达元素,例如从L2层可以之间从2到13只需要2步,而L1层从2到13需要4步,L0层需要5步。 **跳跃列表的层数最好为Logn层** ## 2.2 查找 ### 2.2.1 查找key=13 1. 从顶层L3开始查找,从sentinel开始遍历(从头开始),到达第一个结点13 2. 将key=13与元素相比较,结果相同说明已经找到该元素 ![[Snipaste_2023-02-23_10-00-41 13.png]] ### 2.2.2查找key=8 1. 从顶层L3开始查找,从sentinel开始遍历(从头开始),到达第一个结点13,key<13,进入L2层 2. 从L2层开始,到达第一个结点2,key>2,说明key在2的右边。到达L2层第二个结点13,key<13,进入L1层 3. 从L1层开始,到达第一个结点2,key>2,说明key在2的右边。到达L1层第二个结点10,key<10,进入L0层 4. 从L0层开始,依次遍历,找到key=8,结束 ![[Snipaste_2023-02-23_10-00-41 14.png]] ### 2.2.3 查找key=20 1. 从顶层L3开始查找,从sentinel开始遍历(从头开始),到达第一个结点13,key>13,由于L3层只有13这个结点所有进入L2层。 2. 从L2层开始,由于key>13,所以从L2的13结点查找,到达26,key<26,说明key在13~26之间,向下进入L1层 3. 从L1层13结点开始查找,向右找到Key=20,结束 ![[Snipaste_2023-02-23_10-00-41 15.png]] ## 2.3 插入 ### 2.3.1 插入key=9 1. 查找前先找到插入位置,过程看2.2节,如图所示找到L0层需要4个步骤 ![[Snipaste_2023-02-23_10-00-41 16.png]] 2. 记录每个步骤起点位置 ![[Snipaste_2023-02-23_10-00-41 17.png]] 3. 创建新结点9,高度随机,随机次数由(层数-1)决定 ![[Snipaste_2023-02-23_10-00-41 18.png]] 4. 将新结点与原有结点连接,此时新结点插入完毕,且进行了排序 ![[Snipaste_2023-02-23_10-00-41 19.png]] # 三、实践 跳跃列表用于Redis的zset,作为有序去重队列使用 # 四、总结 特点: 1. 插入和查找都快 2. 有些元素可以同时处于多层,例如13,同时处于L3、L2、L1、L0层 3. 元素每次上升的概率都会乘以50%,例如从L0到L1为50%,L1到L2为50%,那么从L0到L2为25% 4. 列表中元素越多能够进入的层次越深,进入顶层的概率越大 5. **一个元素如果位于顶层,那么从L0到顶层都会存在,例如结点13** 跳跃列表是一个金字塔结构,从顶层到L0元素越来越少。如果查找的元素刚好位于顶层,那么效率就很高