數據結構之跳表

跳表(Skip List)是一種基于概率平衡的數據結構,通過多層有序鏈表實現高效的查找、插入和刪除操作。它在最壞情況下時間復雜度為 (O(n)),但通過隨機化設計,平均時間復雜度可優化至 (O(\log n)),與平衡二叉搜索樹性能相當,但實現更為簡單。

1. 什么是跳表?

想象一下,你有一本非常厚的電話簿,里面的名字是按字母順序排列的。現在你想找 “Zhang San” 這個名字。

  • 方法一(鏈表/線性查找):從第一頁的第一個名字 “A” 開始,一頁一頁地翻,直到找到 “Z”。這非常慢。
  • 方法二(跳表):聰明的電話簿出版商在書的側面貼上了一些“索引標簽”。比如:
    • 第一個標簽指向 “B” 開頭的部分。
    • 第二個標簽指向 “G” 開頭的部分。
    • 第三個標簽指向 “M” 開頭的部分。
    • 第四個標簽指向 “S” 開頭的部分。
    • 第五個標簽指向 “Z” 開頭的部分。

現在你找 “Zhang San”:

  1. 你先看側面的標簽,發現 “Zhang” 的首字母 “Z” 在 “S” 和 “Z” 之間。于是你直接翻到 “S” 標簽指向的頁面。
  2. 從 “S” 開始,你繼續向后翻,因為 “Z” 在 “S” 之后。你可能還會看到一些更細分的標簽,比如 “T”, “W”, “X” 等,幫助你更快地定位。
  3. 很快,你就找到了 “Z” 開頭的部分,然后在這個小范圍內順序查找,最終找到 “Zhang San”。

跳表就是這種“帶有多級索引的有序鏈表”。它通過在原始有序鏈表之上建立多級“索引”層,來大幅提升查找效率,使得查找、插入、刪除操作都能在接近對數的時間復雜度內完成。


2. 為什么需要跳表?

跳表是為了解決有序鏈表的痛點而誕生的。

  • 有序鏈表的優點
    • 插入和刪除操作非常快,只需要修改相鄰節點的指針,時間復雜度為?O(1)在找到位置后。
  • 有序鏈表的致命缺點
    • 查找效率極低。由于不能像數組那樣通過下標隨機訪問,查找一個元素必須從頭節點開始逐個遍歷,時間復雜度為?O(n)。當數據量很大時,這個性能是無法接受的。

我們希望有一種數據結構,既能保持鏈表高效的插入/刪除特性,又能擁有像平衡二叉搜索樹那樣?O(log n)?級別的查找效率。

跳表就是答案之一。它通過增加空間(建立索引)來換取時間,完美地平衡了查找、插入、刪除的性能。


3. 跳表的結構與原理

一個跳表由以下幾部分構成:

  1. 基礎層:最底層是一個標準的有序鏈表,包含了所有的元素。
  2. 索引層:在基礎層之上,有多層稀疏的“索引”鏈表。
    • 每一層都是一個有序鏈表。
    • 第?L+1?層的元素是第?L?層元素的一個子集
    • 上層的每個節點,都有一個指針指向其在下層中相同的節點。
  3. 頭節點:所有層的鏈表都共享一個頭節點,方便從最高層開始查找。
  4. 概率性:一個節點出現在多少層索引中,是通過**“拋硬幣”**這樣的隨機算法決定的。這保證了跳表的平衡性,避免了像平衡樹那樣復雜的旋轉操作。

結構示意圖:

假設我們有基礎鏈表?1 <-> 3 <-> 4 <-> 5 <-> 7 <-> 8 <-> 10

一個可能的跳表結構如下:

Level 3:  ------------------------> 8 ------------------------> NULL|                        |
Level 2:  ----> 3 ----------------> 8 ------------------------> NULL|     |                  |
Level 1:  ----> 3 ------> 5 ------> 8 ------> 10 -------------> NULL|     |        |        |        |
Level 0:  -> 1 <-> 3 <-> 4 <-> 5 <-> 7 <-> 8 <-> 9 <-> 10 -> NULL

解讀:

  • Level 0?是基礎層,包含所有元素。
  • Level 1?包含了?{3, 5, 8, 10},它們是 Level 0 的一個子集。
  • Level 2?包含了?{3, 8},是 Level 1 的一個子集。
  • Level 3?只包含了?{8}
  • 每個上層的節點都通過一個向下的指針連接到下層對應的節點。

4. 操作詳解

a. 查找

查找是跳表最核心的操作,插入和刪除都依賴于它。查找過程遵循“先高層后低層,先大步后小步”的原則。

目標:查找元素?5

  1. 從最高層開始:從頭節點?Head?的 Level 3 開始。
  2. Level 3 查找
    • 當前節點是?Head,下一個節點是?8
    • 5 < 8,說明?5?在?Head?和?8?之間。不能在 Level 3 繼續向右走了。
    • 向下:移動到 Level 2 的?Head?節點。
  3. Level 2 查找
    • 當前節點是?Head,下一個節點是?3
    • 5 > 3,可以繼續向右走。移動到節點?3
    • 在節點?3,它的下一個節點是?8
    • 5 < 8,說明?5?在?3?和?8?之間。不能在 Level 2 繼續向右走了。
    • 向下:移動到 Level 1 的節點?3
  4. Level 1 查找
    • 當前節點是?3,下一個節點是?5
    • 5 == 5找到目標!

如果查找?6

  1. … (過程同上,直到 Level 1 的節點?5)
  2. 在 Level 1 的節點?5,下一個節點是?8
  3. 6 < 8,不能向右。
  4. 向下:移動到 Level 0 的節點?5
  5. Level 0 查找
    • 當前節點是?5,下一個節點是?7
    • 6 < 7,且?6 != 5,說明?6?不存在。

查找路徑總結Head(L3) -> Head(L2) -> 3(L2) -> 3(L1) -> 5(L1)

b. 插入

插入分為兩步:查找位置?和?隨機建層

目標:插入元素?6

  1. 查找插入位置

    • 按照查找?6?的方法,找到它在 Level 0 中的前驅節點。從上面的查找過程可知,6?應該插入在?5?和?7?之間。我們需要記錄下每一層中?6?的前驅節點,這里主要是 Level 0 的?5
    • 在實際操作中,我們會在查找過程中維護一個?update?數組,記錄每一層中待插入位置的前驅節點。
  2. 隨機決定層數

    • 這是跳表的精髓。我們通過一個隨機函數(比如,模擬拋硬幣)來決定新節點?6?要“晉升”到多少層。
    • 算法:初始化層數?level = 1。然后循環,每次有?p?(通常?p=1/2?或?1/4) 的概率?level++,直到失敗為止。level?不能超過跳表當前的最大層數。
    • 假設我們“拋硬幣”的結果是:第一次成功(level=2),第二次成功(level=3),第三次失敗。那么新節點?6?的層數就是?3
  3. 插入節點并更新指針

    • 如果新節點的層數?3?大于當前跳表的最大層數(比如是?2),則需要增加一個新的 Level 3 層。
    • 從?level=3?開始,逐層向下插入新節點:
      • Level 3:將?6?插入到?update[3]?(即?Head) 之后。Head -> 6
      • Level 2:將?6?插入到?update[2]?(即?3) 之后。3 -> 6
      • Level 1:將?6?插入到?update[1]?(即?5) 之后。5 -> 6
      • Level 0:將?6?插入到?update[0]?(即?5) 之后。5 -> 6 -> 7
    • 同時,要確保新節點?6?在每一層的實例都通過?down?指針連接起來。
c. 刪除

刪除操作比插入簡單,因為它不需要隨機建層。

目標:刪除元素?5

  1. 查找待刪除節點

    • 首先查找?5。在查找過程中,同樣需要記錄每一層中?5?的前驅節點(update?數組)。
    • 如果找不到?5,則刪除失敗。
  2. 逐層刪除

    • 從?5?存在的最高層開始(比如 Level 1),逐層向下刪除。
    • 在每一層,修改前驅節點(update[i])的?next?指針,使其指向?5?的后繼節點。
    • Level 1update[1]?是?3,將?3->next?從指向?5?改為指向?8
    • Level 0update[0]?是?4,將?4->next?從指向?5?改為指向?6
    • 這樣,5?在所有層中的引用都被移除了,垃圾回收器會自動回收內存。
  3. (可選)清理空層:如果刪除某個節點后,最高層只剩下一個頭節點,可以考慮移除這一層以節省空間。


5. 性能分析

  • 時間復雜度

    • 查找、插入、刪除:平均時間復雜度均為?O(log n)
    • 最壞情況:如果運氣極差,所有節點都在同一層,那么跳表就退化成了普通鏈表,時間復雜度為?O(n)。但由于層數是隨機決定的,這種情況的概率極低,可以忽略不計。
  • 空間復雜度

    • 平均空間復雜度為?O(n)
    • 每個節點被包含在第?i?層索引中的概率是?p^(i-1)。一個節點平均包含的指針數是?1/(1-p)
    • 當?p = 1/2?時,每個節點平均有?1 + 1/2 + 1/4 + ... = 2?個指針(一個?next,一個?down)。所以總空間開銷大約是基礎鏈表的 2 倍,即?O(2n) = O(n)

6. 跳表的優缺點

優點:

  1. 性能均衡:查找、插入、刪除的性能都非常優秀,都是 O(log n)。
  2. 實現簡單:相比于紅黑樹、AVL樹等平衡二叉搜索樹,跳表的實現邏輯要簡單得多,沒有復雜的旋轉和顏色變換操作。
  3. 天然有序:底層是有序鏈表,非常適合需要范圍查詢的場景(如 “查找所有 score 在 100 到 200 之間的元素”)。
  4. 并發友好:由于跳表的修改操作(插入、刪除)通常只影響局部節點,不像平衡樹那樣可能引起全局性的結構調整,因此更容易實現高效的并發控制。Redis 的作者就曾評價,跳表在并發環境下比平衡樹更有優勢。

缺點:

  1. 空間開銷:需要額外的空間來存儲多層索引,空間復雜度高于普通鏈表和平衡樹(雖然都是 O(n),但常數因子更大)。
  2. 非最壞情況保證:性能是概率性的,雖然最壞情況概率極低,但不像平衡樹那樣能提供硬性的性能保證。

7. 實際應用

跳表最著名的應用是在?Redis?中。

  • Redis 的有序集合:Redis 的 ZSET (Sorted Set) 數據結構,在元素數量較少時使用?ziplist(壓縮列表)來節省內存,當元素數量超過閾值(zset-max-ziplist-entries)時,會轉換為?跳表 + 哈希表?的實現。

    • 跳表:負責按?score?排序,支持高效的范圍查找、按 rank 查找等操作。
    • 哈希表:負責建立?member?到?score?的映射,支持 O(1) 復雜度的按?member?查找?score
    • 這種組合完美地發揮了兩種數據結構的優勢。
  • LevelDB / RocksDB:這些著名的鍵值存儲引擎在其內部內存表(MemTable)的實現中也使用了跳表。


8. 與其他數據結構的對比

特性跳表平衡二叉搜索樹 (如紅黑樹)哈希表
查找O(log n) 平均O(log n) 最壞O(1) 平均
插入O(log n) 平均O(log n) 最壞O(1) 平均
刪除O(log n) 平均O(log n) 最壞O(1) 平均
有序性天然有序,范圍查詢高效天然有序,范圍查詢高效無序,不支持范圍查詢
實現復雜度相對簡單復雜(旋轉/變色)簡單
空間開銷較高 (約 2n)較低 (n)可能有額外開銷(解決沖突)
并發友好性(局部修改)較低(結構調整可能影響大)需要處理哈希沖突和擴容

總結:

跳表是一種非常精巧的數據結構,它用一種簡單而優雅的方式,在有序鏈表的基礎上通過“空間換時間”和“隨機化”思想,實現了與平衡樹相媲美的對數級性能。它實現簡單、性能均衡、并發友好,特別適合作為內存數據庫索引的底層實現,是程序員工具箱中一個非常有價值的工具。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/98078.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/98078.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/98078.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

線程概念,控制

一、線程概念 線程概念&#xff1a;進程內部的一個執行流&#xff0c;輕量化。 觀點&#xff1a;進程是系統分配資源的基本單位&#xff0c;線程是CPU調度的基本單位。 在理解線程之前&#xff0c;我們在談一下虛擬地址空間。 我們都知道進程是通過頁表將虛擬地址轉化為物理地址…

RabbitMQ 高可用實戰篇(Mirrored Queue + Cluster + 持久化整合)

RabbitMQ 高可用實戰篇&#xff08;Mirrored Queue Cluster 持久化整合&#xff09;1. 前言 在生產環境中&#xff0c;單節點 RabbitMQ 容易因故障導致消息丟失或業務中斷。 通過高可用隊列、集群部署和持久化策略&#xff0c;可以保證 消息可靠性、節點容錯和持續服務。 本文…

支持向量機:從理論到實踐

支持向量機&#xff1a;從理論到實踐 文章目錄支持向量機&#xff1a;從理論到實踐一。理論概述1. 線性可分支持向量機1.1 基本概念與數學形式1.2 函數間隔與幾何間隔1.3 間隔最大化與優化問題1.4 拉格朗日對偶理論與求解1.5 支持向量與決策函數2. 近似線性可分數據&#xff08…

LVS與Keepalived詳解(二)LVS負載均衡實現實操

文章目錄前言一、LVS-DR 模式詳解1.1 數據包流向分析1.2 DR 模式的特點二、LVS-DR 集群部署實戰2.1 環境準備2.2 配置負載調度器&#xff08;Director Server&#xff09;2.3 配置節點服務器&#xff08;Real Server&#xff09;2.4 測試驗證三、前期回顧3.1 LVS 三種工作模式及…

歸一化實現原理

歸一化&#xff08;Normalization&#xff09;是一種將數據轉換到相同尺度的預處理技術&#xff0c;它通常用于讓不同特征&#xff08;或數據項&#xff09;具有相同的量綱或范圍。在聯邦學習中&#xff0c;歸一化可以用來處理非獨立同分布&#xff08;Non-IID&#xff09;**數…

企業級實戰:構建基于Qt、C++與YOLOv8的模塊化工業視覺檢測系統

一、概述 在追求高效與精密的現代制造業中&#xff0c;自動化光學檢測&#xff08;AOI&#xff09;已成為保障產品質量的核心技術。傳統的質檢流程往往受限于人工效率與主觀判斷&#xff0c;難以滿足大規模、高精度的生產需求。本文旨在研發一套完整的、企業級的工業視覺異常檢…

【目標檢測】metrice_curve和loss_curve對比圖可視化

代碼如下&#xff1a; import warnings warnings.filterwarnings(ignore)import os import pandas as pd import numpy as np import matplotlib.pylab as pltpwd os.getcwd()names [model1, model2, model3,ours]plt.figure(figsize(10, 10))plt.subplot(2, 2, 1) for i in …

【LeetCode hot100|Week2】滑動窗口,子串

筆記用于個人復習和鞏固&#xff0c;題解非原創&#xff0c;參考LeetCode官方題解以及各個大佬的解法&#xff0c;希望給大家帶來幫助&#xff0c;同時筆記也能督促我學習進步 這周主要把滑動窗口和子串的題目刷了一遍 文章目錄Week2D1 滑動窗口209. 長度最小的子數組713. 乘積…

vue2純前端對接海康威視攝像頭實現實時視頻預覽

vue2純前端對接海康威視攝像頭實現實時視頻預覽一、環境準備二、代碼集成1.1 準備webrtcstreamer.js&#xff0c;粘貼即用&#xff0c;不用做任何修改1.2 封裝視頻組件&#xff0c;在需要視頻的地方引入此封裝的視頻組件即可&#xff0c;也是粘貼即用&#xff0c;注意其中impor…

Android 設置禁止截圖和禁止長截圖

1.禁止截圖 在 Activity 代碼中 , 可以在調用 setContentView 函數之前 ,為 Window 窗口對象 設置 LayoutParams.FLAG_SECURE 標志位 , 可以禁止對本界面進行截屏 ,Window 窗口對象 , 可通過 getWindow 方法獲取 ,核心代碼如下 :getWindow().setFlags(LayoutParams.FLAG_SECUR…

AR 巡檢在工業的應用|阿法龍XR云平臺

AR 巡檢的應用覆蓋電力、石油化工、智能制造、軌道交通、冶金等對設備可靠性和安全性要求極高的行業&#xff0c;具體場景包括&#xff1a;電力行業變電站內設備的狀態檢查&#xff1a;通過 AR 眼鏡掃描設備&#xff0c;實時顯示設備額定參數、歷史故障記錄、實時傳感器數據&am…

【C++】STL詳解(七)—stack和queue的介紹及使用

? 堅持用 清晰易懂的圖解 代碼語言&#xff0c; 讓每個知識點都 簡單直觀 &#xff01; &#x1f680; 個人主頁 &#xff1a;不呆頭 CSDN &#x1f331; 代碼倉庫 &#xff1a;不呆頭 Gitee &#x1f4cc; 專欄系列 &#xff1a; &#x1f4d6; 《C語言》&#x1f9e9; 《…

深度學習周報(9.8~9.14)

目錄 摘要 Abstract 1 LSTM相關網絡總結與對比 1.1 理論總結 1.2 代碼運行對比 2 量子計算入門 3 總結 摘要 本周首先總結了LSTM、Bi-LSTM與GRU的區別與優缺點&#xff0c;對比了三者實戰的代碼與效果&#xff0c;還另外拓展了一些循環神經網絡變體&#xff08;包括窺視…

Quat 四元數庫使用教程:應用場景概述

基礎概念 四元數是一個包含四個元素的數組 [x, y, z, w]&#xff0c;其中 x,y,z表示虛部&#xff0c;w 表示實部。單位四元數常用于表示3D空間中的旋轉。 1. 創建和初始化函數 create() - 創建單位四元數 應用場景&#xff1a;初始化一個新的四元數對象&#xff0c;通常作為其他…

【Java后端】Spring Boot 多模塊項目實戰:從零搭建父工程與子模塊

如何用 Spring Boot 搭建一個父工程 (Parent Project)&#xff0c;并在其中包含多個子模塊 (Module)&#xff0c;適合企業級項目或者需要分模塊管理的場景。Spring Boot 多模塊項目實戰&#xff1a;從零搭建父工程與子模塊在日常開發中&#xff0c;我們經常會遇到這樣的需求&am…

企業級AI會議系統技術實現:快鷺如何用AI重構會議全流程

摘要 本文深度解析快鷺AI會議系統的核心技術架構&#xff0c;重點探討其在語音識別、自然語言處理、數據集成和安全防護等方面的技術實現。通過對比傳統會議系統的技術痛點&#xff0c;分析快鷺AI如何通過技術創新實現會議籌備時間減少67%、數據調取速度提升100倍的顯著效果。…

【CSS學習筆記3】css特性

1css三大特性 1.1層疊性&#xff1a;就近原則&#xff0c;最新定義的樣式 1.2繼承性&#xff1a;子標簽集成父標簽的樣式&#xff0c;如文本和字號 行高的繼承&#xff1a;不加單位指的是當前文字大小的倍數 body {font: 12px/1.5 Microsoft YaHei;color: #be1313;} div {…

[C語言]常見排序算法①

1.排序的概念及常見的排序算法排序在咱們日常生活中十分的常見&#xff0c;就好比是網上購物的時候通常能夠選擇按照什么排序&#xff0c;比如價格、評論數量、銷量等。那么接下來咱們就來了解一些關于排序的概念。排序&#xff1a;所謂排序&#xff0c;就是使一串記錄&#xf…

文獻閱讀筆記:RS電子戰測試與測量技術文檔

信息來源&#xff1a;羅德與施瓦茨&#xff08;Rohde & Schwarz&#xff09;公司關于電子戰&#xff08;Electronic Warfare, EW&#xff09;測試與測量解決方案專業技術文檔。 該文檔由臺灣地區應用工程師Mike Wu撰寫&#xff0c;核心圍繞電子戰基礎、雷達系統、實戰應用及…

別再糾結 Postman 和 Apifox 了!這款開源神器讓 API 測試更簡單

別再糾結 Postman 和 Apifox 了&#xff01;這款開源神器讓 API 測試更簡單&#x1f525; 作為一名開發者&#xff0c;你是否還在為選擇 API 測試工具而糾結&#xff1f;Postman 太重、Apifox 要聯網、付費功能限制多&#xff1f;今天給大家推薦一款完全免費的開源替代方案 ——…