跳跃表(跳表)是什么

为什么要有跳表

正常链表只能一个一个往下走但是如果我直到我的目标位置就在链表的中部但是我还得一步一步走过去很浪费时间,所以跳表就是在正常链表的基础上添加了多步跳跃的指针。

什么是跳表

跳表(Skip List)是一种概率型的数据结构,它是基于链表的,通过创建多个层次的链表来加快搜索速度。每个节点不仅有指向下一节点的指针,还可能有指向更高层次节点的指针,从而实现快速跳跃。跳表的时间复杂度为 O(log n) 级别,适用于插入、删除和查找操作。

演示图只是为了方便,实际跳表并不一定是二分的,一个节点最初始在最底层(L0),他有75%的概率不增长层数有25%的概率往上增长一层,后续也是如此25%概率增长一层,我们的层数基本控制在log n。

以下源码来自 redis7.0
​
#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^64 elements */
#define ZSKIPLIST_P 0.25   /* Skiplist P = 1/4 */
​
/* 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) {
    static const int threshold = ZSKIPLIST_P*RAND_MAX;
    int level = 1;
    while (random() < threshold)
        level += 1;
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

跳表的基本结构

跳表由以下几个部分组成:

  1. 表头(head):负责维护跳跃表的节点指针。

  2. 跳跃表节点:保存着元素值,以及多个层。

  3. :保存着指向其他元素的指针。高层的指针越过的元素数量大于等于低层的指针,为了提高查找的效率,程序总是从高层先开始访问,然后随着元素值范围的缩小,慢慢降低层次。

  4. 表尾:全部由 NULL 组成,表示跳跃表的末尾。

跳表的实现

在Redis中,跳表由 zskiplistNodezskiplist 两个结构定义。其中 zskiplistNode 结构用于表示跳跃表节点,而 zskiplist 结构则用于保存跳跃表节点的相关信息,比如节点的数量,以及指向表头节点和表尾节点的指针等等。

zskiplistNode 结构
  • 后退指针(backward):指向当前节点的前一个节点。

  • 分值(score):用于排序,跳跃表中的所有节点都按分值大小进行排序。

  • 成员对象(ele):即真正要往链表中存放的对象。

  • 层(level):每个节点都包含很多层,每一层指向的都是同一个对象。每一层包含一个前进指针和一个跨度(span),前进指针指向同一层的下一个节点,跨度表示两个节点之间的距离。

typedef struct zskiplistNode {
    //Zset 对象的元素值
    sds ele;
    //元素权重值
    double score;
    //后退指针
    struct zskiplistNode *backward;
  
    //节点的level数组,保存每层上的前向指针和跨度
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned long span;
    } level[];
} zskiplistNode;
zskiplist 结构
  • header:指向跳跃表的表头节点。

  • tail:指向跳跃表的表尾节点。

  • level:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)。

  • length:记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内)。

跳表的操作

跳表支持以下基本操作:

  1. 插入(Insert):插入一个元素时,从最高层开始,查找到当前节点的目标插入位置,随机生成它的层数(25%概率增长一层),然后将元素插入到跳表中并更新指针。需要使用一个数组记录该节点在各层的上一节点,用于跟新指针。

  2. 删除(Delete):删除元素时,需要最高层级开始找到对应的节点,删除并更新相关层级指针。

  3. 查找(Search):查找元素时,从高层开始向下查找,直到找到元素或到达底层。

跳表是一种以空间换时间的方案,它通过增加索引层数来提高查找效率,但同时也会占用更多的内存。在Redis中,跳表主要用于实现有序集合(Sorted Set),它能够保证元素的快速查找和有序存储。

### 跳表(Skip List)是一种基于链数据结构,它通过引入多层索引结构来提升查找效率。跳表的核心思想是通过在链的基础上增加多级索引,使得查找、插入和删除操作的时间复杂度可以达到 O(log n) 的量级,与红黑树处于同一级别,同时在实现上比红黑树更简单,这是其一大优势[^2]。 #### 数据结构 跳表的基本组成单位是节点(Node),每个节点包含以下内容: - **数据**:存储的键值对。 - **指针数组**:每个节点可以有多个指针,用于指向后续节点。指针的数量由节点的层数(Level)决定。 跳表的层数是随机生成的,在插入新节点时,系统会通过概率算法决定该节点的层数。通常情况下,一个节点的层数越高,它在整个跳表中的“跳跃能力”越强,能够跨越更多的节点,从而加快查找速度[^1]。 #### 原理 跳表的工作原理可以类比于现实中的一栋多层建筑的楼层索引。假设你需要在一栋高楼中寻找某个人,如果从一楼逐层查找,效率会很低。但如果每层楼都有一个指示牌,告诉你当前楼层的最大编号住户,以及下一楼层的跳转位置,就可以快速定位目标楼层[^1]。 具体操作如下: - **查找**:从最高层开始,逐步向下查找,直到找到目标节点或确定目标不存在。 - **插入**:先找到插入位置,生成一个新节点,并随机决定该节点的层数,然后更新相关指针。 - **删除**:找到目标节点后,将其从所有层级中移除,并调整前后节点的指针关系。 跳表的随机化特性使得其在平均情况下的性能现良好,避免了链退化为线性结构的问题[^2]。 #### 应用场景 跳表适用于需要高效支持插入、删除和查找操作的场景,尤其在以下方面现突出: - **数据库索引**:跳表可以用于实现数据库中的索引结构,支持快速查找和范围查询。由于跳表的有序性,它特别适合用于需要频繁更新的索引结构[^3]。 - **缓存系统**:跳表的高效查找和更新能力使其在缓存管理中被广泛应用,例如 Redis 中的有序集合(Sorted Set)底层就使用了跳表来实现[^5]。 - **内存数据库**:跳表的实现相对简单,且在内存中的操作效率高,因此在内存数据库或高性能数据结构中被广泛采用。 #### 示例代码 下面是一个简化的跳表节点和跳表结构的 Python 实现: ```python import random class Node: def __init__(self, key, level): self.key = key self.forward = [None] * (level + 1) # 每一层的指针 class SkipList: def __init__(self, max_level, p): self.MAX_LEVEL = max_level self.P = p self.header = self.create_node(self.MAX_LEVEL, -1) self.level = 0 def create_node(self, level, key): return Node(key, level) def random_level(self): level = 0 while random.random() < self.P and level < self.MAX_LEVEL: level += 1 return level def insert(self, key): update = [None] * (self.MAX_LEVEL + 1) current = self.header for i in range(self.level, -1, -1): while current.forward[i] and current.forward[i].key < key: current = current.forward[i] update[i] = current current = current.forward[0] if current is None or current.key != key: new_level = self.random_level() if new_level > self.level: for i in range(self.level + 1, new_level + 1): update[i] = self.header self.level = new_level new_node = self.create_node(new_level, key) for i in range(new_level + 1): new_node.forward[i] = update[i].forward[i] update[i].forward[i] = new_node def search(self, key): current = self.header for i in range(self.level, -1, -1): while current.forward[i] and current.forward[i].key < key: current = current.forward[i] current = current.forward[0] if current and current.key == key: return True return False def delete(self, key): update = [None] * (self.MAX_LEVEL + 1) current = self.header for i in range(self.level, -1, -1): while current.forward[i] and current.forward[i].key < key: current = current.forward[i] update[i] = current current = current.forward[0] if current and current.key == key: for i in range(self.level + 1): if update[i].forward[i] != current: break update[i].forward[i] = current.forward[i] while self.level > 0 and self.header.forward[self.level] is None: self.level -= 1 ``` 该实现中,`random_level()` 函数用于随机生成节点的层数,`insert()` 用于插入新节点,`search()` 用于查找节点,`delete()` 用于删除节点。通过多层索引的方式,跳表在插入、查找和删除操作中都保持了较高的效率。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值