比链表快、比红黑树简单!跳表的原理、操作、应用及面试全解析

比链表快、比红黑树简单!跳表的原理、操作、应用及面试全解析

首页游戏大全时间跳跃侦探更新时间:2024-04-21

跳表(SkipList)是一种随机化的数据结构,由William Pugh在1989年发表的论文《Skip lists: a probabilistic alternative to balanced trees》中提出,它通过在有序链表上增加多级索引实现快速查询的效果。

本文从跳表到底是为了解决什么问题、单线程下跳表的查询&删除&添加、多线程下跳表的查询&删除&添加、跳表和红黑树的对比、跳表在Havenask&redis&开源软件&业务场景中的应用、跳表常见的面试题等十三个方面进行全面解析。

一 跳表到底是为了解决什么问题?

跳表的发明是为了解决链表在查找效率上的问题。

传统的链表在查找元素时需要从头节点开始遍历整个链表,时间复杂度为O(n)。而跳表通过增加多级索引,使得查找元素时可以沿着多个路径进行跳跃,从而提高了查找效率。跳表的查找时间复杂度可以达到O(log n),与二分查找的时间复杂度相同。跳表的空间复杂度是O(n)。

跳表的发明对于数据结构的发展具有重要的意义,它提供了一种新的思路和方法来解决链表在查找效率上的问题。跳表不仅提高了搜索性能,同时也可以提高插入和删除操作的性能。因此,跳表得到了广泛的应用。

二 单线程下跳表的添加

如果是单链表只需要找出待插入节点的前一个节点即可,但在跳表中不光要插入到原始链表中,在他上面的某些层也有可能需要插入,实现方式有随机性和确定性两种选择,其中随机性一般比较常见。比如要在跳表中插入一个元素,可以随机生成一个数字 level ,从 level 层往下每层都要插入。所以跳表中节点不光有 next 属性,还有 down 属性,他指向下一个节点的引用,先来看下跳表的节点类。

在跳表中我们还需要定义一个最大值 MAX_LEVEL ,就是跳表的最大层级数不能超过这个值。我们再来看下索引层级的随机函数,他主要用于在插入节点的时候从第几层开始插入,他是随机的,越往上机率越小,这也符合跳表的特性,越往上节点越少,最大值不能超过 MAX_LEVEL 。

插入操作较为复杂,核心点是需要考虑索引的维护,如果严格按照上一层索引个数是当前层索引个数的 1/2 规则,在插入的过程中会频繁涉及大范围索引重建,对性能影响较大。

跳表舍弃了理想状态的索引结构,而是采用随机的方式决定新增节点在上层是否需要建立索引,在保证一定索引格式的同时也极大缩小了索引维护的性能开销。

插入节点的过程一般是由下往上,从底层开始插入,如何找到待插入节点的前驱节点进行插入操作,这边可以使用栈将定位待插入位置的过程中途经的下行节点记录下来。另外如果插入的元素不断向上建立索引,最终导致索引层数超过了原跳表的最大层数,需要同时维护新的 head 指针。流程如下:

下图展示了插入【节点 6 】的过程,其中因为新增节点的索引高度超出了原来索引的最大高度,所以需要对应新增一个 head 节点;绿色节点则作为下行节点被记录下来。

三 单线程下跳表的查询

查找节点的过程相对简单,设置一个临时节点 cur = head,流程如下:

下图中红色节点展示了查找【节点 8】 的路线:

四 单线程下跳表的删除

删除操作的核心有两点:

设置 cur = head,流程如下:

下图展示了【节点 7】被删除的过程,其中红色节点是查找前驱节点的路线,自顶向下逐层删除节点。

五 多线程下跳表的查询

跳表线程安全的操作往往借助CAS操作来完成,CAS操作的底层是乐观锁的机制,在进行修改操作以前,会记录修改前的数值,修改操作生效时会对比这个值,如果相同,CAS操作才能生效,反之进行失败重试。

一个线程安全的跳表在进行查询时,需要确保在查询过程中不会被其他线程修改,以避免数据不一致的问题。以下是一种可能的线程安全的跳表查询方法:

  1. 获取锁:首先,需要获取跳表的读写锁或互斥锁,以确保在查询过程中不会被其他线程修改。
  2. 定位目标元素:根据查询条件,从跳表的最高层开始逐层向下遍历,直到找到目标元素或到达最低层。
  3. 检查目标元素:在找到目标元素后,需要检查其是否存在,以及是否满足查询条件。
  4. 返回结果:如果目标元素存在且满足查询条件,则返回该元素;否则,返回null或适当的默认值。
  5. 释放锁:最后,释放获取的锁,以便其他线程可以访问跳表。

需要注意的是,在多线程环境下,查询操作可能会被其他线程的修改操作打断,因此需要确保在查询过程中不会被其他线程修改。此外,为了提高查询效率,可以在跳表中使用哈希表等其他数据结构来存储元素,以便快速定位目标元素。

六 多线程下跳表的添加

对于跳表的数据结构而言,节点的增加是分层进行的,首先,最底层的链表储存有所有的节点,所以在这一层一定会新增一个节点,而上层链表是否需要增加节点是由随机数决定的,并不能在插入操作的瞬间就决定出来。如果想增加k值为4的节点,我们就需要优先锁定其前面的所有节点,如下图所示:

红色部分圈起来的是所有需要锁定的节点,锁定之后,先插入最底层的节点,之后随机从下往上确定是否需要继续差插入节点。插入过程结束后,解锁锁定的红色节点。

七 多线程下跳表的删除

在跳表中,节点的删除是从上往下进行的,并且在锁定被删除节点的同时,还需要锁定其前面的节点。我们以单链表为例来说明这个问题:

如下所示的链表,在不锁定前置节点的情况下,如果我们需要并发删除B与C节点,可能会出现以下问题:

为了解决上面出现的问题,我们就需要同时锁定删除节点的上一个节点。推广到跳表的结构中也是一样的,在删除一个节点时,我们需要自上而下的目标节点,在删除某一节点的同时,锁定其前一个节点。

以删除节点3为例,我们需要首先锁定蓝色框中的两个节点,在完成删除操作之后,锁定之后删除红色框中的两个节点,以保证并发的安全性。

八 跳表和红黑树的对比

“Redis为什么用跳表用不红黑树”在百度搜索中有1200万条相关结果

归纳总结下,跳表大概有四类优势

但比较尴尬的是,这些说法并没有数据支持,有些仅泛泛而谈。本文针对如上提到的四类跳表优势,进行量化分析。

1 跳表比红黑树性能好或相当

理由:跳表在新增删除时只需要链接前后节点,而红黑树则需要多轮的左旋和右旋

验证方案:用1000万个不重复的随机数分别进行增删查等操作

验证代码:语言使用Java,红黑树使用TreeMap,跳表使用ConcurrentSkipListMap

验证结果:

验证结论:跳表的耗时在增删查时,均比红黑树慢

但,ConcurrentSkipListMap是线程安全的,里面涉及到乐观锁和CAS锁,这也会增加一部分耗时,那么,再次使用普通的SkipListMap进行测试

再次验证结果:

再次验证结论:除了删除时跳表比红黑树快之外,在新增和查询时均慢于红黑树

即:跳表比红黑树性能好或相当, 不成立

2 跳表的范围查找更快

理由:跳表是排好序的,范围查找可以持续往下遍历,红黑树就相对麻烦些,如先上再往右,性能就变低啦

验证方案:生成1000万个不重复的随机数,然后分别构建红黑树和跳表,然后各自取出第100万-200万间的数据

验证结果:跳表比红黑树少耗时27us,即0.027ms,差异可忽略不计

即:跳表的范围查找更快,也貌似不怎么靠谱

3 跳表更节约内存

理由:跳表平均包含1.33个指针,红黑树则是2个指针。但该理由好像并站不住脚,如下是跳表和红黑树的节点结构

验证方案:生成1000万个不重复的随机数,然后分别构建红黑树和跳表

验证操作:在控制台打开JVIS,选择测试的进程

TreeMap$Entry是红黑树的内存占用情况,总计为27%

ConcurrentSkipListMap$Node和ConcurrentSkipListMap$Index是跳表的内存占用情况,总计为28.5%

即:跳表更节约内存,不成立

4 跳表更容易实现

这个属于难者不会,会者不难,还是看看redis原作者给的说法吧,redis的作者是Antirez(安提雷兹),全名Sandro hawser,是一位意大利的程序员,他给出的理由如下:

其中就提到,跳表更容易实现、调试,且还举了个例子:由于跳表的简单性,作者收到一个补丁(已经在redis master中),其中包含在O(logN)中实现ZRANK的增强跳表,它只需要对代码进行少量修改。且作者认为关于Append Only的持久性和速度,以更多代码和更多复杂性为代价去优化redis并不是一个好主意。

最终的结论:

在redis中使用跳表而非红黑树的原因是跳表比红黑树更容易实现,而非性能和内存。另外,跳表很容易实现无锁并发,而红黑树很难做到

九 跳表在阿里开源搜索引擎Havenask的应用

化繁为简,将复杂的查询变为只是查询包含A或者B这样单个字的文档。我们直接以查询的字作为 key 去倒排索引表中检索,得到的 posting list 就是结果了。

若要检索A且B的文档,则可以先分别用两个 key 去倒排索引中检索,这样会得到两个不同的 posting list。第一个posting list中的文档都包含了A字,第二个posting list中文档都包含了B字。那么,如果一个文档在两个posting list都存在,则找出了A且B的文档。

在这个过程中,posting list的查询则是很耗费时间的事情,在此采用的则是通过跳表来加速查询

十 跳表在Redis的应用

ZSet结构同时包含一个字典和一个跳跃表,跳跃表按score从小到大保存所有集合元素。字典保存着从member到score的映射。这两种结构通过指针共享相同元素的member和score,不会浪费额外内存。

typedef struct zset { dict *dict; zskiplist *zsl; } zset;

ZSet中的字典和跳表布局:

跳表是一个概率型的数据结构,元素的插入层数是随机指定的。Willam Pugh在论文中描述了它的计算过程如下:

重复第 2 步,直至生成的r >p 为止,此时的 lvl 就是要插入的层数。

论文中生成随机层数的伪码:

在Redis中对跳表的实现基本上也是遵循这个思想的,只不过有微小差异,看下Redis关于跳表层数的随机源码src/z_set.c:

/* Returns a random level for the new skiplist node we are going to create. * The return value of this function is between 1 and ZSKIPLIST_MAXLEVEL * (both inclusive), with a powerlaw-alike distribution where higher * levels are less likely to be returned. */ int zslRandomLevel(void) { int level = 1; while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF)) level = 1; return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL; }

其中两个宏的定义在redis.h中:

#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^32 elements */ #define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */

可以看到while中的:

(random()&0xFFFF) < (ZSKIPLIST_P*0xFFFF)

第一眼看到这个公式,因为涉及位运算有些诧异,需要研究一下Antirez为什么使用位运算来这么写?

最开始的猜测是random()返回的是浮点数[0-1],于是乎在线找了个浮点数转二进制的工具,输入0.5看了下结果:

可以看到0.5的32bit转换16进制结果为0x3f000000,如果与0xFFFF做与运算结果还是0,不符合预期。

我印象中C语言的math库好像并没有直接random函数,所以就去Redis源码中找找看,于是下载了3.2版本代码,也并没有找到random()的实现,不过找到了其他几个地方的应用:

看到这里的取模运算,后知后觉地发现原以为random()是个[0-1]的浮点数,但是现在看来是uint32才对,这样Antirez的式子就好理解了。

ZSKIPLIST_P*0xFFFF

由于ZSKIPLIST_P=0.25,所以相当于0xFFFF右移2位变为0x3FFF,假设random()比较均匀,在进行0xFFFF高16位清零之后,低16位取值就落在0x0000-0xFFFF之间,这样while为真的概率只有1/4,更一般地说为真的概率为1/ZSKIPLIST_P。

对于随机层数的实现并不统一,重要的是随机数的生成,在LevelDB中对跳表层数的生成代码是这样的:

template <typename Key, typename Value> int SkipList<Key, Value>::randomLevel() { static const unsigned int kBranching = 4; int height = 1; while (height < kMaxLevel && ((::Next(rnd_) % kBranching) == 0)) { height ; } assert(height > 0); assert(height <= kMaxLevel); return height; } uint32_t Next( uint32_t& seed) { seed = seed & 0x7fffffffu; if (seed == 0 || seed == 2147483647L) { seed = 1; } static const uint32_t M = 2147483647L; static const uint64_t A = 16807; uint64_t product = seed * A; seed = static_cast<uint32_t>((product >> 31) (product & M)); if (seed > M) { seed -= M; } return seed; }

可以看到leveldb使用随机数与kBranching取模,如果值为0就增加一层,这样虽然没有使用浮点数,但是也实现了概率平衡。

十一 跳表在开源软件中的应用
  1. Apache Lucene:Lucene是一个开源的全文搜索引擎,它使用跳表来存储和搜索词汇表。通过使用跳表,Lucene可以在O(log N)的时间内完成词汇表的查找和搜索操作。
  2. Apache Cassandra:Cassandra是一个开源的分布式NoSQL数据库,它使用跳表来提高查询效率。Cassandra中的数据以键值对的形式存储在跳表中,通过跳表可以快速地定位到特定的键值对。
  3. Apache Hive:Hive是一个开源的数据仓库工具,它使用跳表来存储和查询数据。Hive中的数据以表格的形式存储在跳表中,通过使用跳表,Hive可以在O(log N)的时间内完成数据的查询和过滤操作。
  4. Apache Flink:Flink是一个开源的流处理和批处理框架,它使用跳表来提高查询效率。Flink中的数据以键值对的形式存储在跳表中,通过跳表可以快速地定位到特定的键值对。
  5. Apache Storm:Storm是一个开源的分布式实时计算系统,它使用跳表来处理数据流。Storm中的数据以元组的形式存储在跳表中,通过使用跳表,Storm可以在O(log N)的时间内完成数据的处理和计算操作。
  6. Elasticsearch:Elasticsearch是一个基于Lucene构建的开源、分布式、RESTful搜索引擎,它使用跳表来提高查询效率。Elasticsearch中的数据以文档的形式存储在跳表中,通过使用跳表,Elasticsearch可以在O(log N)的时间内完成文档的查询和过滤操作。
  7. Apache Samza:Samza是一个开源的分布式流处理框架,它使用跳表来处理数据流。Samza中的数据以消息的形式存储在跳表中,通过使用跳表,Samza可以在O(log N)的时间内完成数据的处理和计算操作。
  8. Apache Beam:Beam是一个开源的批处理和流处理框架,它使用跳表来提高查询效率。Beam中的数据以记录的形式存储在跳表中,通过使用跳表,Beam可以在O(log N)的时间内完成数据的处理和计算操作。
  9. Apache Flink Table:Flink Table是Flink的一个模块,它使用跳表来存储和查询数据。Flink Table中的数据以表格的形式存储在跳表中,通过使用跳表,Flink Table可以在O(log N)的时间内完成数据的查询和过滤操作。
  10. Apache Apex:Apex是一个开源的分布式大数据处理引擎,它使用跳表来提高查询效率。Apex中的数据以元组的形式存储在跳表中,通过使用跳表,Apex可以在O(log N)的时间内完成数据的处理和计算操作。
十二 跳表在业务场景的应用

跳表算法在实际应用中有广泛的用途,特别是在需要高效的有序集合操作的场景。以下是一些跳表算法的常见应用:

  1. 数据库索引:跳表可以用作数据库索引的一种实现方式,用于加速数据库的查询操作。它可以提供类似于 B 树索引的功能,但实现起来相对简单,适用于轻量级数据库或内存数据库。
  2. 内存分配器:在动态内存分配中,可以使用跳表来管理分配的内存块,以快速定位可用的内存块并进行高效的内存分配和释放操作。
  3. 网络路由表:跳表可以用于实现路由表,用于路由器中的IP地址查找。通过将IP地址映射到跳表中,可以在路由查找过程中实现快速的匹配操作。
  4. 排序算法优化:在某些排序算法中,如快速排序的变种,可以使用跳表来优化算法的部分步骤,从而减少比较次数和数据移动。
  5. 高性能的集合操作:跳表可以用于实现各种集合操作,如并集、交集、差集等,以及范围查询操作。这些操作在日志处理、事件处理等场景中经常用到。
  6. 有序集合数据结构:在编程语言或库中,跳表可以作为一种有序集合数据结构的实现方式,提供高效的插入、删除和查找操作。
  7. 高性能索引结构:在搜索引擎、数据库系统或其他需要高性能索引结构的应用中,跳表可以用作一种快速检索数据的数据结构。
  8. 排名和排行榜:跳表可以用于实现排行榜功能,支持快速的分数排名查询和更新。
  9. 高性能的搜索引擎:跳表可以用于搜索引擎的倒排索引,其中每个词汇对应一个跳表,用于快速定位包含该词汇的文档。
  10. 近似查询:跳表可以用于实现近似查询,例如在一个包含相似字符串的集合中,可以使用跳表来加速相似度搜索。
  11. 时间序列数据库:跳表可以用于存储和查询时间序列数据,如传感器数据、股票价格等。它能够支持按时间范围查询的高效操作。
  12. 缓存数据结构:跳表可以用于实现高性能缓存的数据结构,用于存储缓存项并支持快速的插入、删除和查找操作。
  13. 文本编辑器中的自动补全:跳表可以用于实现文本编辑器中的自动补全功能,通过将单词按字母拆分存储在跳表中,可以快速匹配用户输入的前缀。
  14. 分布式系统中的分片索引:在分布式系统中,可以使用跳表来维护分片数据的索引,从而加速分片数据的查询和定位。
  15. 图形算法:在一些图形算法中,跳表可以用于高效地搜索和处理图中的一些特定关系,如最短路径、连通性等。
  16. 并发数据结构:跳表可以用于实现一些并发数据结构,如有序集合,同时支持并发的插入、删除和查找操作。
  17. 分布式哈希表:跳表可以用于分布式环境中的哈希表,用于分布式缓存、分布式计算等场景。
  18. 数据压缩和编码:在一些数据压缩和编码算法中,跳表可以用于快速检索编码后的数据。
  19. 游戏开发:跳表可以用于游戏中的碰撞检测、空间划分等操作,帮助加速游戏内的元素查询和交互。
  20. 音频和视频编解码:在音频和视频编解码的过程中,可能需要快速地定位和访问特定的时间点或帧,跳表可以用于支持这些快速访问的需求。
  21. 事件调度:跳表可以用于事件调度和定时器管理,以及处理时间相关的操作。
  22. 密码学:在某些密码学应用中,跳表可以用于生成和管理随机数序列,或者用于一些密钥管理操作。
  23. 匹配算法:在字符串匹配、图模式匹配等领域,跳表可以用于加速匹配操作。
  24. 机器学习和数据挖掘:在一些机器学习和数据挖掘算法中,跳表可以用于处理排序、查找等操作。
  25. 网络协议处理:在网络协议栈中,跳表可以用于处理各种数据包,实现高效的路由查找、过滤和处理。
  26. 任务调度器:在操作系统或分布式系统中,跳表可以用于任务调度和资源管理。
  27. 智能搜索和推荐系统:在智能搜索引擎、推荐系统中,跳表可以用于存储和检索相关的内容。
  28. 数据流处理:在流式数据处理中,跳表可以用于管理和检索数据流中的某些关键信息。
  29. 路由和导航系统:在地图应用和GPS导航系统中,跳表可以用于高效地存储和查找地理位置信息,以支持路由计算和导航操作。
  30. 社交网络和关系图:在社交网络分析和关系图中,跳表可以用于管理和查询用户之间的关系,例如好友关系、关注关系等。
  31. 数据同步和版本控制:跳表可以用于实现数据同步和版本控制系统中的快速查找和比较操作。
  32. 区块链和加密货币:在区块链技术中,跳表可以用于管理区块和交易的快速查询和验证。
  33. 自然语言处理:在某些自然语言处理任务中,如文本搜索和关键词匹配,跳表可以用于高效地检索和处理文本数据。
  34. 电子商务平台:跳表可以用于电子商务平台中的商品搜索、过滤和排序功能,以及处理用户的购物车和订单等操作。
  35. 生物信息学:在基因序列分析和生物信息学领域,跳表可以用于处理DNA或蛋白质序列的快速查询和比对。
  36. 分布式缓存和分布式存储:在分布式系统中,跳表可以用于管理分布式缓存和分布式存储中的数据定位和查询。
  37. 物联网应用:在物联网应用中,跳表可以用于管理传感器数据和设备信息,以及支持实时监控和控制操作。
  38. 多媒体应用:在多媒体应用中,跳表可以用于存储和查询图片、音频和视频等多媒体数据。
  39. 物理引擎:在游戏开发中,物理引擎需要高效地检测碰撞和物体位置,跳表可以用于加速这些计算。
  40. 数据结构教学和实验:跳表算法也常常用作数据结构教学的示例,用于讲解有序数据结构的实现和操作。
  41. 分布式一致性:在分布式系统中,跳表可以用于维护节点间的顺序,以实现一致性算法中的排序需求。
  42. 音乐播放列表:跳表可以用于音乐播放列表中的歌曲排列,以支持按时间、艺术家、流派等方式的快速查找。
  43. 机票和酒店预订系统:在线预订系统可以使用跳表来高效地存储和查找可用的机票或酒店房间。
  44. 虚拟化和容器技术:在虚拟化和容器技术中,跳表可以用于高效地查找资源分配情况,以及管理虚拟机或容器的状态。
  45. 交通管理系统:跳表可以用于交通管理系统中的交通流量分析、车辆定位等操作。
  46. 日志处理和数据分析:跳表可以用于存储和查询大规模日志数据,以及支持实时的数据分析操作。
  47. 医疗健康领域:在医疗健康领域,跳表可以用于管理患者数据、医疗记录等信息。
  48. 广告投放系统:在线广告投放系统可以使用跳表来管理广告位和广告素材,以支持广告的快速展示。
  49. 智能城市管理:在智能城市系统中,跳表可以用于处理城市设备、传感器数据和基础设施状态,以支持实时监控和管理。
  50. 人工智能推理:在人工智能和知识图谱的推理过程中,跳表可以用于高效地查找和匹配概念、关系和知识点。
  51. 高性能日志库:在高性能日志库中,跳表可以用于高效地存储、索引和查询大量日志数据。
  52. 数字化图书馆:在数字化图书馆中,跳表可以用于高效地管理和检索图书、文章和文献。
  53. 智能交通系统:跳表可以用于智能交通系统中的车辆定位、道路拥堵分析等操作。
  54. 环境监测:在环境监测领域,跳表可以用于处理传感器数据,如空气质量、水质等数据。
  55. 航空航天工程:在航空航天领域,跳表可以用于飞行数据管理、航班路径规划等任务。
  56. 农业科技:在农业科技中,跳表可以用于管理农作物种植、气候数据等信息。
  57. 虚拟现实和增强现实:在虚拟现实和增强现实应用中,跳表可以用于空间定位、对象交互等操作。
  58. 智能家居:在智能家居系统中,跳表可以用于设备控制、状态管理等操作。
十三 跳表常见的面试题

题目1:跳表中的每一层代表什么?为什么需要多层次?

答案:跳表中的每一层代表了数据在内存中的不同分布情况。在跳表中,每一层都保存了前一层中某些元素的指针,从而形成了层次结构。多层次的原因是为了在插入、删除和查找操作中提供更好的时间复杂度。通过增加层次,可以减少需要搜索的元素数量,从而提高效率。

题目2:在跳表中查找元素时,为什么只需查看最底层即可找到元素?

答案:在跳表中,每一层都保存了前一层中某些元素的指针。因此,通过从最底层开始,沿着指针向上搜索,可以逐步缩小搜索范围,最终找到目标元素。在最底层中,元素是按照链表的形式排列的,因此只需查看最底层即可找到元素。

题目3:请解释一下跳跃表的时间复杂度,以及在哪些场景下它的效率最高?

答案:跳跃表的时间复杂度取决于查找、插入和删除操作的复杂度。在最坏情况下,跳跃表的查找、插入和删除操作的时间复杂度为O(n),其中n为跳跃表的长度。但在平均情况下,跳跃表的时间复杂度为O(log n)。跳跃表适用于需要频繁进行插入、删除和查找操作的数据结构,且元素之间具有部分有序性或全有序性。

题目4:如果一个跳跃表的长度为n,那么它最多需要多少个指针来保存跳跃表?

答案:如果一个跳跃表的长度为n,那么它最多需要log n层。在最坏情况下,每一层都需要保存n个指针。因此,最多需要log n层 * n个指针 = n * log n个指针来保存跳跃表。

题目5:在跳跃表中插入元素时,如何确定应该插入到哪一层?

答案:在跳跃表中插入元素时,可以根据元素的值来决定应该插入到哪一层。通常,将元素插入到最底层中,然后根据元素的值逐步向上插入到其他层中。这样可以保证元素的插入顺序正确,并减少插入操作的复杂度。

题目6:跳跃表在实现删除操作时需要注意哪些问题?

答案:在跳跃表中实现删除操作时,需要注意以下几点:

找到要删除的元素所在的层;删除该层中的元素;调整该层中其他元素的指针;如果该层中所有元素的指针都为空,则可以删除该层。需要注意的是,在删除操作中需要保持跳跃表的平衡性,避免出现空指针或不平衡的情况。

题目7:跳跃表中的每一层高度如何确定?对于不同层数的跳跃表,它们的时间复杂度有何不同?

答案:跳跃表中的每一层高度可以根据需要确定,通常与元素值的范围有关。例如,如果元素值在1到100之间,那么可以设置3层跳跃表,每层的高度分别为2、4和8。不同层数的跳跃表的时间复杂度有所不同。在最坏情况下,跳跃表的时间复杂度为O(n)。随着层数的增加,时间复杂度逐渐降低,但同时内存消耗也会增加。因此,需要根据实际情况选择合适的层数。

题目8:在实现跳跃表时,如何保证内存的利用率?

答案:在实现跳跃表时,可以通过以下方法保证内存的利用率:

尽可能减少不必要的内存消耗;对于空指针的层或链表,可以将其合并或删除;在进行插入和删除操作时,及时调整内存的使用情况。

题目9:请解释一下跳跃表的平衡因子,以及如何通过平衡因子来调整跳跃表的性能?

答案:跳跃表的平衡因子是指每一层的高度与该层中元素的数量的比例。通过调整平衡因子的大小,可以影响跳跃表的性能。如果平衡因子设置得较小,则可以减少内存消耗和提高查询效率;但如果平衡因子设置得较大,则可能会导致查询效率降低。因此,需要根据实际情况选择合适的平衡因子大小。

题目10:请谈谈你对跳跃表的理解,以及在什么场景下会使用跳跃表?

答案:跳跃表是一种高效的数据结构,它可以在O(log n)的时间复杂度内完成插入、删除和查找操作。跳跃表适用于需要频繁进行插入、删除和查找操作的数据结构,且元素之间具有部分有序性或全有序性。在实际应用中,跳跃表可以用于实现缓存、索引、数据库等场景中

十四 参考资料

[1] 图解|深入理解跳表及其在Redis中的应用:https://mp.weixin.qq.com/s/0Pm1n-tHGSRUht8bWVGjWQ

[2]【迎难学字】跳表(Skip List)原理、操作、特点及58个常见应用场景:https://mp.weixin.qq.com/s/9puKkN2rjlETfMhWNwfFZQ

[3] 搜索引擎系列004-倒排索引:https://mp.weixin.qq.com/s/VgpV_478I58LClkJKF_-9w

[4] 动画解析Redis为什么用跳表而不是红黑树:https://www.bilibili.com/video/BV1wv411c71N

[5] 跳跃表的基本实现原理:https://mp.weixin.qq.com/s/a8S1abzeEwNeiRZ37iJRAA

[6] SkipList跳表介绍与并发安全:https://juejin.cn/post/7107280326858113060

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved