redis sort set2

「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战」。

2.1 跳跃表的形成过程

通过上文我们认识到了一个单链表的结构,并通过分析插入、查询得知他们的时间复杂度都是O(N),如果我们想降低时间复杂度,我们需要怎么做呢?

第一种:转换为哈希表,第二种:转换成平衡树,第三种:转换为B-树,但是这些数据结构都有其不合适的地方(后续3.9章节会有跟各个数据结构的对比情况),如果我们就是希望以链表为基础的数据结构,有其他的合适的数据结构吗?

答案是有的,首先我们回想一下我们的书的目录的结构,下面是(图2.0)是时间简史的目录,假如我们想看黑洞这章节,我们能够很容易知道从267页开始就是讲的黑洞,我们也能够很容易的知道第302页就是第八章宇宙的起源和命运的开始,这其实是我们提前把章节打上了一个索引

book-list

那对于链表呢?我们也可以给它加一下索引来提高我们的查询效率。假如我们现在有一个有序链表,其元素为1、3、5、7、10、12、15、19,我们可以在链表的基础上,每两个结点提取一个结点作为索引,在通过索引层的结点去原始链表去找元素,如图2.1**:

skiplist

这样我们找一个数字12,没有索引的流程是1 -> 3 -> 5 -> 7 -> 10 -> 12返回,现在的流程是从一级索引开始查:1 -> 5 -> 10 -> 12,查询次数从6次减少到了4次,甚至我们还可以再基于一级索引每两个结点再提取一个结点作为二级索引,如图2.1

skip-list-two-level

这样我们找一个数字12,流程是: 1 -> 10 -> 12,查询次数从4次减少到了3次,不过我们的数据量现在只有 8 个元素,假如说我们有几千个元素那查询效果就非常明显了。像这种在原来有序链表的基础上增加了多级索引来提高查询效率数据结构,我们称之为跳表,也叫跳跃表,哪不知道大家有没有一个疑问,我们怎么去确定我们有多少级的索引(后见章节3.2)?我们跳表的查询,插入时间复杂度是怎么样的呢?下面我们先推导一下跳表的时间复杂度。

2.2 跳跃表的时间复杂度推导

假如我们要找元素12,我们来观察一下我们的查询路径,如图2.3所示,在二级索引和元素1、10对比没找到,在一级索引又进行了元素15对比,还是没有找到,最后再原始链表层又和12进行了对比,找到了元素12

two-level-search

假如我们的原始链表有N个结点,假如没两个结点我们提取一个结点,可以得出以下结论:

1、那么一级索引结点个数N / 2,那么假如我们的索引有M阶,第M阶索引就是:N / 2^M

2、假如第M阶的结点个数是 2,那么可以得出 N / 2^m = 2,得出时间复杂度为M = log(N) - 1

3、假如我们每次遍历 K个结点,那么得出时间复杂度是O(K * log(N))

4、K一般是个常数,所以最后的时间复杂度是O(log(N))

2.3 跳跃表的插入过程

我们来看一下简单的插入过程,我们插入一个元素11,首先我们先执行查询过程,第一步,跟二级索引的1做比较:如图2.4

first-one

发现1结点小于11新插入的结点,则完后对比,再跟10作对比,发现10结点也小于 11结点,再往后对比,发现10后面是NULL,如图2.5

two-one

则下降一级索引,然后再往后跟15比较,发现1511大,则再下降一级索引,然后再跟12做对比,发现12也大于11,那我们就找到了我们的插入位置,就是在1012之间,如图2.6和图2.7:

first-three

12做对比:如图2.7

first-three

最后我们找到插入位置在元素10和元素12之间,然后就可以进行插入动作了,插入完成后,整个跳表如下图2.8所示:

first-foure

我们也推导一下插入过程的时间复杂度,通过插入过程我们知道,想要插入一个元素,我们要先找到插入的位置,这个过程是一个查询过程,经过上一节我们的查询一个结点的时间复杂度是O(log(N)),那我们插入一个结点的时间复杂度也是O(log(N))

通过本小节,我们了解到跳表的形成过程,时间复杂度的推动,插入、查询过程,下面我们通过研究Redis的有序集合的底层存储,来探究一下,跳表是怎么在Redis的应用的。

猜你喜欢

转载自juejin.im/post/7066451142297223204