跳表(Skip List) 介绍

跳表详解

跳表(Skip List) 是一种动态数据结构,最早由 William Pugh 于 1990 年提出。它通过在有序链表的基础上引入多级索引结构,使得在有序链表中可以快速进行查找、插入和删除操作。跳表的平均时间复杂度为 O(log n),在最坏情况下为 O(n)

1. 跳表结构

跳表的核心思想是通过建立多级“跳跃”索引来减少遍历节点的数量。我们可以把跳表想象成一个多层的链表结构:

  • 最底层是一个普通的有序链表,保存所有元素。
  • 第二层是对最底层链表的一部分节点建立的索引,索引跨度为 2。
  • 第三层对第二层再建立索引,索引跨度为 4,以此类推。

这种多级索引结构使得查找、插入和删除操作都能够通过从上往下跳跃,逐步缩小查找范围。

2. 跳表的时间复杂度

  • 查找:在每一层索引中,从左到右进行顺序查找,如果找到合适区间,再下降到下一层继续查找,最终找到目标元素。平均时间复杂度为 O(log n)
  • 插入:在找到插入位置后,按照一定概率,更新插入元素的多级索引。
  • 删除:删除操作也是先找到目标元素,然后删除元素及其相关的索引。

3. 使用场景

跳表适用于以下场景:

  • 动态有序集合:当需要快速地进行插入、删除和查找操作,并且数据需要保持有序时,跳表是非常好的选择。
  • 分布式系统中的有序集合:比如 Redis 的 Sorted Set(有序集合)底层就使用了跳表,跳表能够为有序数据提供高效的插入、删除、和查找。
  • 内存数据库、缓存:在内存数据库或缓存中,如果需要经常进行查找和范围查询操作,跳表的性能表现良好,且比树结构更简单。

4. 示例代码

下面是用 Python 实现的跳表结构,示例中包括跳表的插入、查找、删除功能:

import random

class Node:
    def __init__(self, value=None, level=0):
        self.value = value
        self.forward = [None] * (level + 1)  # 创建一个 forward 列表,表示该节点的“前进”指针

class SkipList:
    MAX_LEVEL = 16  # 最大索引层数
    P = 0.5  # 随机生成层数的概率

    def __init__(self):
        self.level = 0  # 当前的索引层数
        self.header = Node(None, self.MAX_LEVEL)  # 跳表的头节点

    def random_level(self):
        level = 0
        while random.random() < self.P and level < self.MAX_LEVEL:
            level += 1
        return level

    def insert(self, value):
        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].value < value:
                current = current.forward[i]
            update[i] = current  # 记录该层的最后一个小于待插入值的节点

        # 插入新的节点
        level = self.random_level()  # 随机生成新节点的层数
        if level > self.level:
            for i in range(self.level + 1, level + 1):
                update[i] = self.header  # 新增层索引,指向头节点
            self.level = level

        new_node = Node(value, level)
        for i in range(level + 1):
            new_node.forward[i] = update[i].forward[i]
            update[i].forward[i] = new_node

    def search(self, value):
        current = self.header
        for i in range(self.level, -1, -1):
            while current.forward[i] and current.forward[i].value < value:
                current = current.forward[i]
        current = current.forward[0]
        if current and current.value == value:
            return True
        return False

    def delete(self, value):
        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].value < value:
                current = current.forward[i]
            update[i] = current

        current = current.forward[0]
        if current and current.value == value:
            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

# 测试跳表
if __name__ == "__main__":
    skiplist = SkipList()
    
    # 插入一些数据
    for num in [3, 6, 7, 9, 12, 19, 17]:
        skiplist.insert(num)
    
    # 查找数据
    print(skiplist.search(7))  # 输出 True
    print(skiplist.search(15))  # 输出 False
    
    # 删除数据
    skiplist.delete(6)
    print(skiplist.search(6))  # 输出 False

5. 总结

  • 优点:跳表是一种易于实现的数据结构,平均性能与平衡树相当,但实现相对简单,并且在分布式系统中表现出色。
  • 应用场景:跳表非常适合用于需要频繁查找和插入的有序数据场景,尤其是像 Redis 中的 Sorted Set 这样的数据结构。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容

  • 跳表是一种神奇的数据结构,因为几乎所有版本的大学本科教材上都没有跳表这种数据结构,而且神书《算法导论》、《算法第四...
    fanrui阅读 100,376评论 28 187
  • 只需要对链表稍加改造,就可以支持类似“二分”的查找算法。我们把改造之后的数据结构叫做 跳表 Skip List 跳...
    快乐帅宅阅读 163评论 0 0
  • 第一次看到这种数据结构还是刚接触ocean base架构的时候。粗略扫了几眼,以为是一个简单的二级索引,没有仔细考...
    littlersmall阅读 1,602评论 0 50
  • 首先来思考一个问题。 一个有序链表(下图),搜索,添加,删除的平均时间复杂度是多少? 通过对链表这种数据结构的了解...
    ducktobey阅读 293评论 0 1
  • 我们知道二叉搜索算法能够高效的查询数据,但是需要一块连续的内存,而且增删改效率很低。跳表,是基于链表实现的一种类似...
    币来币往阅读 27,699评论 5 19