深入理解跳表:多層索引加速查找的經典實現

跳表(Skip List)是一種多層有序鏈表結構,通過引入多級索引加速查找,其核心設計類似于“立體高速公路系統”,底層是原始鏈表,上面有各種高度的"高架橋"。

高層道路跨度大,連接遠方節點;底層道路密集,保留所有細節。

跳表不僅能提高搜索性能,同時也可以提高插入和刪除操作的性能。

它在性能上和紅黑樹AVL樹不相上下,但是跳表的原理非常簡單,實現也比紅黑樹簡單很多。

跳表就能讓鏈表擁有近乎的接近二分查找的效率的一種數據結構,其原理依然是給上面加若干層索引,優化查找速度,典型的空間換取時間的方式
在這里插入圖片描述

底層(Level 0)完整的有序單鏈表,包含所有元素,例如 1 → 3 → 5 → 7 → 9。

上層索引(Level 1~N)

  • 每層節點數約為下層的 1/2(概率性生成,如概率P=0.5時,一般情況下為:0.25(redis使用的就是0.25),表示的是上一層的節點數=當前層*0.25)。
  • 高層節點稀疏但跨度大,底層節點密集。

示例結構(3層跳表):

Level 2: 1 ---------------------> 9  
Level 1: 1 -----> 5 -----> 7 -----> 9  
Level 0: 13579  

每個跳表節點包含一個值一個指針數組forward

這個數組長度就是節點的層級數,指向同層下一個節點。

在Redis的實現中,節點還包含后退指針(backward)和跨度(span)信息

1.查詢操作

非常的簡單,思想就是二分思想
在這里插入圖片描述

// search
public SkipNode<T> search(int key) {// 初始化開始節點為頭節點SkipNode<T> p = head;// 在當前的節點不是空的情況下,有三種情況:while (p != null) {if (p.getKey() == key) {// 如果當前節點的key等于要查找的key,則返回當前節點return p;} else if (p.getNext() == null || p.getNext().getKey() >  key) {// 如果右側沒有了,或者右側的key大于要查找的key,則繼續往下找p = p.getDown();} else {p = p.getNext(); // 否則向右找}}return null;
}

2.刪除操作

刪除操作比起查詢稍微復雜一些,但是比插入簡單。

刪除需要改變鏈表結構所以需要處理好節點之間的聯系。對于刪除操作你需要謹記以下幾點:

  1. 刪除當前節點和這個節點的前后節點都有關系
  2. 刪除當前層節點之后,下一層該key的節點也要刪除,一直刪除到最底層
    在這里插入圖片描述

例如上圖刪除10節點,流程如下:

  • team=head 從 team 出發,7 < 10 向右
  • 右側為null只能向下;
  • 右側為10在當前層刪除10節點然后向下繼續查找下一層10節點;
  • 8 < 10 向右;第五步右側為10刪除該節點并且team向下。
  • team為null說明刪除完畢退出循環。
// delete
public void delete(int key) {SkipNode<T> term = head;  // 初始化當前節點while (term != null) {if (term.getNext() == null || term.getNext().getKey() > key) {// 如果當前節點的右側沒有節點,或者當前節點的右側的key大于要刪除的key// 則繼續往下找term = term.getDown();} else {// 否則,當前節點的key等于要刪除的keyif (term.getNext().getKey() == key) {// 如果當前節點的key等于要刪除的key,則將當前節點的next指向當前節點的next的nextterm.setNext(term.getNext().getNext());term = term.getDown();} else {// 否則,當前節點的key不等于要刪除的key,則繼續向右找term = term.getNext();}}}
}

3 插入操作

插入需要考慮是否插入索引,插入幾層等問題。

由于需要插入刪除所以我們肯定無法維護一個完全理想的索引結構,因為它耗費的代價太高。但我們使用隨機化的方法去判斷是否向上層插入索引。

即產生一個[0-1]的隨機數如果小于0.5就向上插入索引,插入完畢后再次使用隨機數判斷是否向上插入索引。運氣好這個值可能是多層索引,運氣不好只插入最底層(這是100%插入的)。

但是索引也不能不限制高度,我們一般會設置索引最高值如果大于這個值就不往上繼續添加索引了。

具體插入步驟:

步驟1:隨機決定層數java

// 為新節點隨機決定層數
int insertLevel = randomLevel(); // 比如隨機得到3層
private int randomLevel() {int level = 1;// 每次有1/2的概率繼續向上while (Math.random() < 0.5 && level < MAX_LEVEL) {//MAX_LEVEL:系統設定的最高層,不是當前的最高層level++;}return level;
}

步驟2:從最高層開始查找

// 從跳表的最高層開始查找,記錄每層的插入位置
Node[] update = new Node[MAX_LEVEL];
Node current = header;// 從最高層向下查找
for (int i = MAX_LEVEL - 1; i >= 0; i--) {while (current.next[i] != null && current.next[i].value < targetValue) {current = current.next[i];}update[i] = current; // 記錄第i層的插入位置
}

步驟3:逐層插入java

// 從底層開始逐層插入
for (int i = 0; i < insertLevel; i++) {newNode.next[i] = update[i].next[i];update[i].next[i] = newNode;
}

具體示例演示:

  • 隨機層數在當前層數內
初始跳表:
Level 3: header ---------------------> 9
Level 2: header -----> 5 -----------> 9  
Level 1: header -----> 5 -----> 7 --> 9
Level 0: header -> 3 -> 5 -> 7 -> 9插入值6,隨機層數為2:步驟1:隨機決定層數 = 2步驟2:從最高層開始查找
- Level 3: header -> 9 (6 < 9),記錄update[3] = header
- Level 2: header -> 5 -> 9 (6 > 5),記錄update[2] = 5
- Level 1: header -> 5 -> 7 (6 < 7),記錄update[1] = 5
- Level 0: header -> 5 -> 7 (6 < 7),記錄update[0] = 5步驟3:逐層插入(只在Level 1Level 0插入)
- Level 1: 5 -> 6 -> 7
- Level 0: 5 -> 6 -> 7結果:
Level 3: header ---------------------> 9
Level 2: header -----> 5 -----------> 9  
Level 1: header -----> 5 -> 6 -----> 7 --> 9
Level 0: header -> 3 -> 5 -> 6 -> 7 -> 9
  • 隨機層數超過最高層
初始跳表:
Level 3: header ---------------------> 9
Level 2: header -----> 5 -----------> 9  
Level 1: header -----> 5 -----> 7 --> 9
Level 0: header -> 3 -> 5 -> 7 -> 9插入值6,隨機層數為5,當前最高層:4步驟1:隨機決定層數 = 5步驟2:從最高層開始查找
- Level 4: header -> null (6 < null),記錄update[4] = header
- Level 3: header -> 9 (6 < 9),記錄update[3] = header
- Level 2: header -> 5 -> 9 (6 > 5),記錄update[2] = 5
- Level 1: header -> 5 -> 7 (6 < 7),記錄update[1] = 5
- Level 0: header -> 5 -> 7 (6 < 7),記錄update[0] = 5步驟3:創建新節點
Node newNode = new Node(6, 5); // 值6,層數5步驟4:從底層開始逐層插入
//由于隨機插入的層數是新的最高層
//所以逐層插入(在Level 4、Level 3、Level 2、Level 1、Level 0插入))- Level 4: header -> 6 -> null
- Level 3: header -> 6 -> 9
- Level 2: header -> 5 -> 6 -> 9
- Level 1: header -> 5 -> 6 -> 7 -> 9
- Level 0: header -> 3 -> 5 -> 6 -> 7 -> 9

為何這樣設計?

  1. 查找需要從高層開始,因為高層跨度大
  2. 插入需要從底層開始,因為需要維護指針關系

為何要限制最高層數:

  1. 內存使用:防止層數過多導致內存浪費
  2. 性能考慮:層數過多反而影響性能
  3. 實用性:實際應用中很少需要超過32層
  4. 概率分析:32層已經足夠處理大量數據

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

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

相關文章

Flutter 視頻播放器——flick_video_player 介紹與使用

在移動端應用中&#xff0c;視頻播放是一個常見的功能場景&#xff0c;例如短視頻、直播、課程、廣告展示等。 Flutter 本身并沒有直接提供視頻播放器組件&#xff0c;而是依賴第三方庫來實現。 今天要介紹的庫是 flick_video_player&#xff0c;它基于 video_player 封裝&…

編寫cmakelists文件常用語句

cmake_minimum_required (VERSION 3.10) 指定最小版本project(XXXX) 指定項目名字 ---------------set(MAIN_EXEC_NAME dwarf_parser) 定義變量${ MAIN_EXEC_NAME } 變量取值set(CMAKE_CXX_STANDARD 14) 指定c14標準&#xff0c;還有11、17、20等標準…

麒麟桌面系統找不到mbr啟動,并重新安裝grub

根據你提供的情況,“麒麟桌面系統找不到MBR啟動”,這通常是由于GRUB引導損壞、MBR記錄丟失或分區表異常導致的。你可以按照以下步驟重新安裝GRUB并修復MBR啟動: ? 步驟一:準備工具 使用銀河麒麟LiveCD或U盤啟動盤(可用Ventoy制作); 啟動電腦,選擇從U盤或光盤進入Live環…

【音頻字幕】構建一個離線視頻字幕生成系統:使用 WhisperX 和 Faster-Whisper 的 Python 實現

一、背景介紹 對于一端沒有字幕外國視頻、字幕&#xff0c;在不懂外語的情況下&#xff0c;怎么獲取相關內容&#xff1f;作為技術宅&#xff0c;怎么自建搭建一個語音轉文字的環境當前AI技術這么發達&#xff1f; 試試 二、系統設計 音頻提取(僅僅是視頻需要該邏輯、本身就是音…

Linux ALSA架構:PCM_OPEN流程 (二)

一 應用端源碼路徑: external\tinyalsa\pcm.c external\tinyalsa\pcm_hw.cstruct pcm *pcm_open(unsigned int card, unsigned int device,unsigned int flags, struct pcm_config *config) {...pcm->ops &hw_ops;pcm->fd pcm->ops->open(card, device,…

tp5的tbmember表閉包查詢 openid=‘abc‘ 并且(wx_unionid=null或者wx_unionid=‘‘)

閉包查詢 tbmember表閉包查詢查詢 openid‘abc并且islose0并且islogout0并且&#xff08;wx_unionidnull或者wx_unionid’&#xff09; Db::table(tbmember)->where([openid>abc,islose>0,islogout>0])->where(function ($query){$query->where(wx_unioni…

邪修實戰系列(3)

1、第一階段邪修實戰總覽&#xff08;9.1-9.30&#xff09; 把第一階段&#xff08;基礎夯實期&#xff09;的學習計劃拆解成極具操作性的每日行動方案。這個計劃充分利用我“在職學習”的特殊優勢&#xff0c;強調“用輸出倒逼輸入”&#xff0c;確保每一分鐘的學習都直接服務…

【GD32】ROM Bootloader、自定義Bootloader區別

Bootloader是應用程序跑起來之前&#xff0c;用于初始化的一段程序&#xff0c;它分為兩種&#xff0c;ROM Bootloader、自定義Bootloader。GD32芯片出廠時預燒錄在ROM中的Bootloader&#xff08;以下簡稱ROM Bootloader&#xff09;和自己編寫的Bootloader&#xff08;以下簡稱…

Linux防火墻-Firewalld

一、 概述 按表現形式劃分&#xff1a; 軟件防火墻&#xff1a; 集成在系統內部&#xff0c;Linux系統&#xff1a; iptables、firewalld、ufw&#xff1b; windows系統下&#xff1a; windows defender 硬件防火墻&#xff1a; 華為防火墻、思科防火墻、奇安信防火墻、深信服防…

【Qt】PyQt、原生QT、PySide6三者的多方面比較

目錄 引言 一、基本定義 二、核心對比維度 1. 編程語言與開發效率 2. 功能與 API 兼容性 3. 性能表現 4. 許可證與商業使用 5. 社區與文檔支持 三、遷移與兼容性 四、適用場景推薦 五、總結對比表 總結 引言 PySide6、PyQt&#xff08;通常指 PyQt5/PyQt6&#xf…

JavaWeb站內信系統 - 技術設計文檔

1. 系統概述1.1 項目背景本系統旨在為企業或社區平臺提供一套完整的站內信解決方案&#xff0c;支持用戶之間的消息發送、接收、管理等功能&#xff0c;提升用戶間的溝通效率。1.2 設計目標實現用戶間消息發送和接收支持一對一和一對多消息發送提供消息狀態跟蹤&#xff08;已讀…

Java基礎 9.10

1.System類常見方法和案例exit&#xff1a;退出當前程序arraycopy&#xff1a;復制數組元素&#xff0c;比較適合底層調用&#xff0c;一般使用 Arrays.copyOf 完成復制數組int[] src{1,2,3};int[] dest new int[3]; System.arraycopy(src, 0, dest, 0, 3);currentTimeMilens&…

詳解flink性能優化

1. 簡介 Apache Flink是一個強大的流處理框架&#xff0c;其性能很大程度上取決于內存的使用效率。在大規模數據處理場景中&#xff0c;合理的內存配置和優化可以顯著提升Flink作業的性能和穩定性。本文將深入探討Flink內存優化的各個方面&#xff0c;包括狀態后端選擇、內存配…

VueFlow的箭頭怎么調整

正好最近用到了VueFlow組件&#xff0c;發現箭頭默認樣式太小&#xff0c;無法體現流程展示&#xff0c;因此翻閱相關資料得出下列方法&#xff0c;有什么更好的方法&#xff0c;大家可以推薦推薦&#xff0c;謝謝。方法1&#xff1a;通過邊&#xff08;Edge&#xff09;的樣式…

【Python】S1 基礎篇 P9 文件處理與異常處理技術

目錄文件讀取操作讀取文件的全部內容相對路徑和絕對路徑逐行訪問文件內容文件寫入操作寫入單行內容寫入多行內容結構化數據的存儲異常處理機制理解異常的工作原理ZeroDivisionError異常示例try-except語句塊的使用else語句塊的正確使用靜默失敗的合理應用本文將深入探討Python中…

分布式事務實戰手冊:從四場業務災難看方案選型與落地陷阱

在分布式系統的穩定性戰役中&#xff0c;數據一致性問題如同潛伏的暗礁。某生鮮電商因分布式事務設計缺陷&#xff0c;在春節促銷期間出現"下單成功但無庫存發貨"的悖論&#xff0c;3小時內產生2300筆無效訂單&#xff0c;客服投訴量激增300%&#xff1b;某銀行轉賬系…

Java算法題中的輸入輸出流

在Java算法題中&#xff0c;處理輸入輸出主要依賴系統流&#xff08;System.in和System.out&#xff09;&#xff0c;常用的方法總結如下&#xff1a; 一、輸入方法&#xff08;讀取系統輸入&#xff09; 主要通過java.util.Scanner類或BufferedReader類實現&#xff0c;適用于…

墨水屏程序

EPD Reader 基于ESP32-C3的電子墨水屏閱讀器&#xff0c;支持ap 配網、sntp 時間同步、txt閱讀、天氣預報、顯示節假日信息、農歷顯示、自動休眠、web配置等功能。這是在另一個項目 一個rust embassy esp32c3 的練習項目-CSDN博客的基礎上修改的 。 界面比較粗糙&#xff0c;以…

Git 創建 SSH 密鑰

1.生成 SSH 密鑰 打開 Git Bash ssh-keygen -t ed25519 -C "your_email@example.com" 把 ”your_email@example.com“ 改成再 github 注冊的郵箱 系統會提示您三次輸入: 第一個提示:Enter file in which to save the key (/c/Users/86189/.ssh/id_ed25519): 直接…

當前 AI 的主流應用場景

當前AI技術已深度滲透至社會各領域,2025年的主流應用場景呈現出行業垂直化、交互自然化、決策自主化三大特征。以下從六大核心領域展開分析,結合最新技術突破與規模化落地案例,揭示AI如何重塑人類生產生活范式: 一、智能辦公與生產力革命 AI正從工具升級為「數字同事」,…