【數據結構】跳表

目錄

1.什么是跳表-skiplist

2.skiplist的效率如何保證?

3.skiplist的實現

3.1節點和成員設計

3.2查找實現

3.3前置節點查找

3.4插入實現

3.5刪除實現

3.6隨機層數

3.7完整代碼

4.skiplist跟平衡搜索樹和哈希表的對比


1.什么是跳表-skiplist

skiplist是由William Pugh發明的,最早出現于他在1990年發表的論文《Skip Lists: A
Probabilistic Alternative to Balanced Trees》。
skiplist,顧名思義,首先它是一個list。實際上,它是在有序鏈表的基礎上發展起來的。如果是一個有序的鏈表,查找數據的時間復雜度是O(N)。

William Pugh開始的優化思路:

1. 假如我們每相鄰兩個節點升高一層,增加一個指針,讓指針指向下下個節點,如下圖b所
示。這樣所有新增加的指針連成了一個新的鏈表,但它包含的節點個數只有原來的一半。由
于新增加的指針,我們不再需要與鏈表中每個節點逐個進行比較了,需要比較的節點數大概
只有原來的一半。

2. 以此類推,我們可以在第二層新產生的鏈表上,繼續為每相鄰的兩個節點升高一層,增加一個指針,從而產生第三層鏈表。如下圖c,這樣搜索效率就進一步提高了。

如圖b中,查找8,從頭結點出發,6比8小,向右走,跳躍到6;8比9小,當前節點向下走,從下面指針出發,7比8小,跳轉到7;8比9小,向下走,走不了了,鏈表中不存在8.

如圖c中,查找19,比9大,向右走,跳躍到9;比21小,向下走,比17大,向右走,跳躍到17,比17大,向右走,跳躍到17,比21小,向下走,跟19相等,找到了。

3. skiplist正是受這種多層鏈表的想法的啟發而設計出來的。實際上,按照上面生成鏈表的方
式,上面每一層鏈表的節點個數,是下面一層的節點個數的一半,這樣查找過程就非常類似
二分查找,使得查找的時間復雜度可以降低到O(log n)
但是這個結構在插入刪除數據的時
候有很大的問題,插入或者刪除一個節點之后,就會打亂上下相鄰兩層鏈表上節點個數嚴格
的2:1的對應關系。如果要維持這種對應關系,就必須把新插入的節點后面的所有節點(也
包括新插入的節點)重新進行調整,這會讓時間復雜度重新蛻化成O(n)

4. skiplist的設計為了避免這種問題,做了一個大膽的處理,不再嚴格要求對應比例關系,而是插入一個節點的時候隨機出一個層數。這樣每次插入和刪除都不需要考慮其他節點的層數,這樣就好處理多了(按時插入刪除,我們仍然需要考慮其他節點對應層指針的連接問題)。細節過程入下圖:

2.skiplist的效率如何保證?

上面我們說到,skiplist插入一個節點時隨機出一個層數,聽起來怎么這么隨意,如何保證搜索時
的效率呢?
這里首先要細節分析的是這個隨機層數是怎么來的。一般跳表會設計一個最大層數maxLevel的限
制,其次會設置一個多增加一層的概率p。那么計算這個隨機層數的偽代碼如下圖:

在Redis的skiplist實現中,這兩個參數的取值為:

p = 1/4
maxLevel = 32

根據前面randomLevel()的偽碼,我們很容易看出,產生越高的節點層數,概率越低。定量的分析
如下:
節點層數至少為1。而大于1的節點層數,滿足一個概率分布。
節點層數恰好等于1的概率為1-p。
節點層數大于等于2的概率為p,而節點層數恰好等于2的概率為p(1-p)。
節點層數大于等于3的概率為p^2,而節點層數恰好等于3的概率為p^2*(1-p)。
節點層數大于等于4的概率為p^3,而節點層數恰好等于4的概率為p^3*(1-p)。
……
因此,一個節點的平均層數(也即包含的平均指針數目),計算如下:
現在很容易計算出:

當p=1/2時,每個節點所包含的平均指針數目為2;
當p=1/4時,每個節點所包含的平均指針數目為1.33。
跳表的平均時間復雜度為O(logN)

這個推導的過程較為復雜,需要有一定的數據功底,有興趣的,可以參考以下文章中的講解:

鐵蕾大佬的博客:http://zhangtielei.com/posts/blog-redis-skiplist.html

William_Pugh大佬的論文:ftp://ftp.cs.umd.edu/pub/skipLists/skiplists.pdf

3.skiplist的實現

1206. 設計跳表 - 力扣(LeetCode)

3.1節點和成員設計

跳表因為每個節點是層數是隨機的,所以我們使用vector容器存儲對應指針。

對于一開始的同節點,我們這里設計成一層高,減少后續不必要遍歷消耗。

struct SkiplistNode
{int _val;vector<SkiplistNode*> _nextV;SkiplistNode(int val, int level):_val(val), _nextV(level, nullptr){}
};
class Skiplist {typedef SkiplistNode Node;
public:Skiplist() {//為了后續層數隨機,這里提供種子srand(time(0));// 頭節點,層數是1_head = new SkiplistNode(-1, 1);}private:Node* _head; // 頭節點size_t _maxLevel = 32;//層數的最大高度double _p = 0.25;/多增加一層的概率p
};

3.2查找實現

依照跳表的設計,當我們查找一個數時,如果當前層的指針指向的數比要查找的數大,那我們就需要向下走,因為鏈表是有序的,比較小的數一定在前面,而且層高一定沒有當前層指向節點高(如果搞就指向這個更小的數了。)所以我們要找比當前層指向節點小的數,需要向下層走。

反之,如果比當前層指向節點值比要查找的數小,說明要查找的數在后面,我們直接沿當前層指針往后跳轉。

	bool search(int target) {Node* cur = _head;int level = _head->_nextV.size() - 1;while (level >= 0){// 目標值比下一個節點值要大,向右走// 下一個節點是空(尾),目標值比下一個節點值要小,向下走if (cur->_nextV[level] && cur->_nextV[level]->_val < target){// 向右走cur = cur->_nextV[level];}else if (cur->_nextV[level] == nullptr || cur->_nextV[level]-> _val > target){// 向下走--level;}else{return true;}}return false;}

3.3前置節點查找

因為跳表中節點的層高沒有嚴格的比例關系,因此一個節點可能由于層高較高,與前后多個節點有關系,如上圖中6有4層,0層上,3指向它,6指向7;1層上頭結點指向6,6指向9;2層上,頭結點指向6,6指向25;3層上頭結點指向6,6指向nullptr。

因此當我們刪除或者增加一個節點,我們就需要建立它每一層上指針與其他節點間關系,同時也需要修改其他其他節點。

這就意味著當前要插入/刪除節點,就需要從整個跳表中找到每一層指針對應的上一個節點,更新指針指向。

解決的思想是利用查找節點過程,因為當一層指針指向空或者比要查找的值大的值時,我們向下走,這同時也意味著此時這層節點就是當前要插入節點的當前層的上一層節點,同時這屆指針指向的下層節點也就是插入節點指針要指向的下一個節點。我們通過查找節點不斷向下走的過程,就能找到插入節點每一層的上一個節點和要指向的下一層節點。

需要注意的是當查找節點比當前節點大,直接跳轉的過程,我們不能更新上一層節點的記錄,因為這是只是同層節點間的跳轉,我們不能確定插入節點當前層的上一個節點。

vector<Node*> FindPrevNode(int num){Node* cur = _head;int level = _head->_nextV.size() - 1;// 插入位置每一層前一個節點指針vector<Node*> prevV(level + 1, _head);while (level >= 0){// 目標值比下一個節點值要大,向右走// 下一個節點是空(尾),目標值比下一個節點值要小,向下走if (cur->_nextV[level] && cur->_nextV[level]->_val < num){// 向右走cur = cur->_nextV[level];}else if (cur->_nextV[level] == nullptr|| cur->_nextV[level]->_val >= num){// 更新level層前一個prevV[level] = cur;// 向下走--level;}}return prevV;}

3.4插入實現

當我們能找到插入節點的每一層對應上一個節點,我們插入節點只需要更新對應的指向,上一個指針指向它,插入節點指向下一個節點。

這里需要注意的是,插入節點的層數我們封裝一個函數來給在最大范圍內的隨機值,跳表的頭結點需要確保是跳表中最高的節點,這樣才不會出現無法跳轉更高層的問題,所以如果當前的隨機值如果比頭節點高,我們就需要更新下頭結點高度。

	void add(int num) {vector<Node*> prevV = FindPrevNode(num);int n = RandomLevel();//隨機層數Node* newnode = new Node(num, n);// 如果n超過當前最大的層數,那就升高一下_head的層數if (n > _head->_nextV.size()){_head->_nextV.resize(n, nullptr);prevV.resize(n, _head);}// 鏈接前后節點for (size_t i = 0; i < n; ++i){newnode->_nextV[i] = prevV[i]->_nextV[i];prevV[i]->_nextV[i] = newnode;}// Print();}

3.5刪除實現

刪除節點后,我們需要根據找到的每一層的上一個節點,更新一下對應指針將刪除節點上一個節點和指向下一個節點相連。

這里加入刪除的節點是最高點,我們更新一下頭結點的高度,避免每次從很高的位置向下遍歷(不過就幾層,相對來說影響不大)。

	bool erase(int num) {vector<Node*> prevV = FindPrevNode(num);// 第一層下一個不是val,val不在表中if (prevV[0]->_nextV[0] == nullptr || prevV[0]->_nextV[0]->_val !=num){return false;}else{Node* del = prevV[0]->_nextV[0];// del節點每一層的前后指針鏈接起來for (size_t i = 0; i < del->_nextV.size(); i++){prevV[i]->_nextV[i] = del->_nextV[i];}delete del;// 如果刪除最高層節點,把頭節點的層數也降一下int i = _head->_nextV.size() - 1;while (i >= 0){if (_head->_nextV[i] == nullptr)--i;elsebreak;}_head->_nextV.resize(i + 1);return true;}}

3.6隨機層數

	//產生隨機層數int RandomLevel(){size_t level = 1;//每一個節點最少一層。// 因為rand() ->[0, RAND_MAX]之間,所以RAND_MAX * _p的數出現概率就是p// 可以視作rand()/RAND_MAX <= pwhile (rand() <= RAND_MAX * _p && level < _maxLevel){++level;}return level;}//C++中的隨機函數庫,使用比較麻煩/*int RandomLevel(){static std::default_random_enginegenerator(std::chrono::system_clock::now().time_since_epoch().count());static std::uniform_real_distribution<double> distribution(0.0,1.0);size_t level = 1;while (distribution(generator) <= _p && level < _maxLevel){++level;}return level;}*/

3.7完整代碼

struct SkiplistNode
{int _val;vector<SkiplistNode*> _nextV;SkiplistNode(int val, int level):_val(val), _nextV(level, nullptr){}
};
class Skiplist {typedef SkiplistNode Node;
public:Skiplist() {srand(time(0));// 頭節點,層數是1_head = new SkiplistNode(-1, 1);}bool search(int target) {Node* cur = _head;int level = _head->_nextV.size() - 1;while (level >= 0){// 目標值比下一個節點值要大,向右走// 下一個節點是空(尾),目標值比下一個節點值要小,向下走if (cur->_nextV[level] && cur->_nextV[level]->_val < target){// 向右走cur = cur->_nextV[level];}else if (cur->_nextV[level] == nullptr || cur->_nextV[level] -> _val > target){// 向下走--level;}else{return true;}}return false;}vector<Node*> FindPrevNode(int num){Node* cur = _head;int level = _head->_nextV.size() - 1;// 插入位置每一層前一個節點指針vector<Node*> prevV(level + 1, _head);while (level >= 0){// 目標值比下一個節點值要大,向右走// 下一個節點是空(尾),目標值比下一個節點值要小,向下走if (cur->_nextV[level] && cur->_nextV[level]->_val < num){// 向右走cur = cur->_nextV[level];}else if (cur->_nextV[level] == nullptr|| cur->_nextV[level]->_val >= num){// 更新level層前一個prevV[level] = cur;// 向下走--level;}}return prevV;}void add(int num) {vector<Node*> prevV = FindPrevNode(num);int n = RandomLevel();Node* newnode = new Node(num, n);// 如果n超過當前最大的層數,那就升高一下_head的層數if (n > _head->_nextV.size()){_head->_nextV.resize(n, nullptr);prevV.resize(n, _head);}// 鏈接前后節點for (size_t i = 0; i < n; ++i){newnode->_nextV[i] = prevV[i]->_nextV[i];prevV[i]->_nextV[i] = newnode;}// Print();}bool erase(int num) {vector<Node*> prevV = FindPrevNode(num);// 第一層下一個不是val,val不在表中if (prevV[0]->_nextV[0] == nullptr || prevV[0]->_nextV[0]->_val !=num){return false;}else{Node* del = prevV[0]->_nextV[0];// del節點每一層的前后指針鏈接起來for (size_t i = 0; i < del->_nextV.size(); i++){prevV[i]->_nextV[i] = del->_nextV[i];}delete del;// 如果刪除最高層節點,把頭節點的層數也降一下int i = _head->_nextV.size() - 1;while (i >= 0){if (_head->_nextV[i] == nullptr)--i;elsebreak;}_head->_nextV.resize(i + 1);return true;}}int RandomLevel(){size_t level = 1;// rand() ->[0, RAND_MAX]之間while (rand() <= RAND_MAX * _p && level < _maxLevel){++level;}return level;}/*int RandomLevel(){static std::default_random_enginegenerator(std::chrono::system_clock::now().time_since_epoch().count());static std::uniform_real_distribution<double> distribution(0.0,1.0);size_t level = 1;while (distribution(generator) <= _p && level < _maxLevel){++level;}return level;}*/private:Node* _head; // 頭節點size_t _maxLevel = 32;double _p = 0.25;
};

4.skiplist跟平衡搜索樹和哈希表的對比

1. skiplist相比平衡搜索樹(AVL樹和紅黑樹)對比,都可以做到遍歷數據有序,時間復雜度也差
不多。skiplist的優勢是:a、skiplist實現簡單,容易控制。平衡樹增刪查改遍歷都更復雜。
b、skiplist的額外空間消耗更低。平衡樹節點存儲每個值有三叉鏈,平衡因子/顏色等消耗。
skiplist中p=1/2時,每個節點所包含的平均指針數目為2;skiplist中p=1/4時,每個節點所包
含的平均指針數目為1.33;


2. skiplist相比哈希表而言,就沒有那么大的優勢了。相比而言a、哈希表平均時間復雜度是
O(1),比skiplist快。b、哈希表空間消耗略多一點。skiplist優勢如下:a、遍歷數據有序
b、skiplist空間消耗略小一點,哈希表存在鏈接指針和表空間消耗。c、哈希表擴容有性能損
耗。d、哈希表再極端場景下哈希沖突高,效率下降厲害,需要紅黑樹補足接力。

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

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

相關文章

html實現右上角有個圖標,鼠標移動到該位置出現手型,點擊會彈出登錄窗口。

寫了一段html代碼實現的效果&#xff1a;實現右上角有個圖標&#xff0c;鼠標移動到該位置出現手型&#xff0c;點擊會彈出登錄窗口。功能實現前端&#xff0c;沒有實現后端。<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF…

STM32G4 電流環閉環(二) 霍爾有感運行

目錄一、STM32G4 電流環閉環(二) 霍爾有感運行2. 霍爾有感運行附學習參考網址歡迎大家有問題評論交流 (* ^ ω ^)一、STM32G4 電流環閉環(二) 霍爾有感運行 2. 霍爾有感運行 文章使用的BLDC在定子側以互差120電角度的位置安裝三個霍爾元件Ha&#xff0c;Hb&#xff0c;Hc。當…

展示框選擇

好的&#xff0c;非常感謝您提供更詳細的項目情況。這是一個非常典型的父子組件通信場景。 根據您的新需求&#xff0c;我將對代碼進行重構&#xff1a; FaultSelect.vue (子組件): 這個組件現在將變得更加“純粹”。它只負責自身的下拉框邏輯&#xff0c;不關心外部按鈕&#…

第5課:上下文管理與狀態持久化

第5課:上下文管理與狀態持久化 課程目標 掌握上下文存儲和檢索策略 學習會話狀態管理 了解數據持久化方案 實踐實現上下文管理系統 課程內容 5.1 上下文管理基礎 什么是上下文管理? 上下文管理是Agent系統中維護和利用歷史信息的能力,包括: 對話歷史:用戶與Agent的交互…

計算機畢業設計 基于大數據技術的醫療數據分析與研究 Python 大數據畢業設計 Hadoop畢業設計選題【附源碼+文檔報告+安裝調試】

博主介紹&#xff1a;?從事軟件開發10年之余&#xff0c;專注于Java技術領域、Python、大數據、人工智能及數據挖掘、小程序項目開發和Android項目開發等。CSDN、掘金、華為云、InfoQ、阿里云等平臺優質作者? &#x1f345;文末獲取源碼聯系&#x1f345; &#x1f447;&…

K8S集群管理(2)

目錄 1.什么是Pod的根容器&#xff1f; 2.解釋Pod的生命周期。 3.Init類型容器有什么特點&#xff0c;主要用途&#xff1f; 4.Sidecar類型容器和Init容器的區別在哪&#xff1f; 5.什么是靜態Pod&#xff1f; 6.說明K8s控制器的作用&#xff1f; 7.什么是ReplicaSet&#xff0…

視頻全模態referring分割:Ref-AVS: Refer and Segment Objects in Audio-Visual Scenes

一、TL&#xff1b;DR 為什么要做&#xff1a;傳統的referring分割無法使用音頻模態&#xff0c;本文提出Reference audio-visual Segmentation本文怎么做&#xff1a;構建首個 Ref-AVS 基準數據集通過充分利用多模態提示&#xff0c;將音頻信息通過和文本融合作為載體&#x…

A股大盤數據-20250916分析

&#x1f4ca; 一、大盤數據深度分析1.1 &#x1f9ee; 市場活躍度與資金流向總成交額&#xff1a;滬深京合計約 2.37萬億元&#xff0c;市場交投活躍&#xff0c;深市成交&#xff08;13516.4億&#xff09;明顯高于滬市&#xff08;9897.9億&#xff09;&#xff0c;顯示中小…

[計算機畢業設計]基于深度學習的噪聲過濾音頻優化系統研究

前言 &#x1f4c5;大四是整個大學期間最忙碌的時光,一邊要忙著備考或實習為畢業后面臨的就業升學做準備,一邊要為畢業設計耗費大量精力。近幾年各個學校要求的畢設項目越來越難,有不少課題是研究生級別難度的,對本科同學來說是充滿挑戰。為幫助大家順利通過和節省時間與精力投…

貪心算法應用:NFV功能部署問題詳解

Java中的貪心算法應用&#xff1a;NFV功能部署問題詳解 1. NFV功能部署問題概述 網絡功能虛擬化(NFV, Network Function Virtualization)是一種將傳統網絡設備功能從專用硬件轉移到虛擬化軟件的技術。在NFV功能部署問題中&#xff0c;我們需要將各種虛擬網絡功能(VNFs)部署到有…

SeriLog測試

安裝Serilog.Sinks.Seq(5.2.3.0)&#xff0c;Serilog.Sinks.File(7.0.0) 下載Seq安裝包并安裝&#xff08;https://datalust.co/download&#xff09; 代碼如下&#xff1a; private Logger _logger;private void button1_Click(object sender, EventArgs e){_logger new Lo…

HarmonyOS 5.0應用開發——V2裝飾器@param的使用

【高心星出品】 文章目錄V2裝飾器param的使用概念使用方法案例V2裝飾器param的使用 概念 在鴻蒙ArkTS開發中&#xff0c;Param裝飾器是組件間狀態管理的重要工具&#xff0c;主要用于父子組件間的單向數據傳遞&#xff0c;這一點與V1中的prop類似。 Param裝飾的變量支持本地…

SLAM | 無人機視覺/激光雷達集群SLAM技術進展綜述

主要內容如下: 無人機集群SLAM技術概述:介紹無人機集群SLAM的基本概念、重要性及面臨的挑戰,使用表格對比不同傳感器配置的特點。 多傳感器融合與協同SLAM架構:分析集中式、分布式和混合式協同架構的特點,使用表格對比不同架構的優缺點。 視覺協同SLAM的技術進展:總結直接…

信息化系統運維文檔資料,運維服務方案,運維巡檢方案

1、系統服務內容?1.1 服務目標?1.2 信息資產統計服務?1.3 網絡與安全系統運維服務?1.4 主機與存儲系統運維服務?1.5 數據庫系統運維服務?1.6 中間件運維服務?2、服務管理制度規范?2.1 服務時間管理?2.2 運維人員行為規范?2.3 現場服務支持規范?2.4 問題記錄與歸檔規…

JavaScript——document對象

DOM 是 document object model&#xff08;文檔對象模型&#xff09;的縮寫。它是一種與平臺、語言無關的接口&#xff0c;允許程序動態地訪問或更新 HTML、XML 文檔的內容、結構和樣式&#xff0c;且提供了一系列的函數和對象來實現增、刪、改、查操作。DOM 對象的一個特點是&…

UART,IIC,SPI總線(通信協議)

嵌 入 式 軟 件 筆 試 題要求&#xff1a;閉卷考試&#xff08;不能翻書、不能開電腦&#xff09;&#xff1b;作答時間50分鐘&#xff1b;共10道題目。volatile的作用有哪些volatile&#xff1a; 防止編譯器對代碼進行優化&#xff0c;直接從內存中取最新的值 應用場景&#x…

通信模組性能調優

通信模組性能調優 1 背景 2 高通平臺軟硬加速 2.1 NSS 2.2 SFE 2.3 PPE 3 CPU 負載均衡設置 3.1 啟用內核 RPS&RFS 功能 3.2 網卡隊列修改建議 3.3 調整負載前后的 CPU 使用對比 3.4 網卡中斷均衡 3.4.1 netdev_max_backlog 3.4.2 中斷綁核 3.5 CPU性能模式 3.6 熱管理 3.7…

消息隊列kafka的事務特性

kafka的java客戶端producer也支持事務消息嗎&#xff1f;具體是啥事務呢&#xff1f; 是的&#xff0c;Kafka的Java客戶端Producer確實支持事務消息。讓我詳細解釋Kafka事務的概念和使用方法。 Kafka事務的主要特點&#xff1a; Producer Transactions&#xff1a;確保多個消息…

用Python實現自動化的Web測試(Selenium)

Python作為數據科學和自動化領域的主流語言&#xff0c;在網絡爬蟲開發中占據著重要地位。本文將全面介紹Python爬蟲的技術棧、實現方法和最佳實踐。爬蟲技術概述網絡爬蟲&#xff08;Web Crawler&#xff09;是一種按照特定規則自動抓取互聯網信息的程序。它可以自動化地瀏覽網…

「Memene 摸魚日報 2025.9.17」上海張江人工智能創新小鎮正式啟動,華為 DCP 技術獲網絡頂會獎項

theme: condensed-night-purple 以下內容包括「人工智能生成內容」 上海張江人工智能創新小鎮正式啟動&#xff0c;華為 DCP 技術獲網絡頂會獎項 &#x1f44f;在昨天&#xff08;2025.9.16&#xff09;&#xff0c;AI領域有這些內容可能值得你關注&#xff1a; 上海張江人工智…