數據結構與算法之美:跳表

?????????Hello大家好!很高興我們又見面啦!給生活添點passion,開始今天的編程之路!

我的博客:<但凡.

我的專欄:《編程之路》、《數據結構與算法之美》、《題海拾貝》、《C++修煉之路》

歡迎點贊,關注!

目錄

1、跳表引入

? ? ? ? 1.1、背景

????????1.2、跳表的基本原理

2、跳表模擬實現

? ? ? ? 2.1、跳表節點

? ? ? ? 2.2、跳表框架

? ? ? ? 2.3、 查詢

????????2.4、 查找前置節點

? ? ? ? 2.5、添加節點

? ? ? ? 2.6、刪除節點

3、跳表的性能分析


1、跳表引入

? ? ? ? 1.1、背景

????????跳表(Skip List)由美國計算機科學家威廉·普(William Pugh)于1989年提出,目的是解決有序鏈表查詢效率低的問題。傳統鏈表查詢時間復雜度為O(n),而跳表通過引入多層索引結構,將查詢、插入和刪除操作的時間復雜度優化至O(log n),同時保持了較高的空間效率。普的論文《Skip Lists: A Probabilistic Alternative to Balanced Trees》奠定了跳表在數據結構領域的地位,成為平衡樹輕量級替代方案


????????1.2、跳表的基本原理

????????跳表的核心思想是通過概率性構建多層索引加速查找。每一層都是有序鏈表,底層包含所有元素,上層逐步稀疏。稀疏與稠密是通過設置概率不同有所差別。每一個跳表的分布都是隨機生成的。查找時從最高層開始,若當前節點的下一個節點值小于目標值,則向右移動;否則向下移動到下一層繼續搜索。插入和刪除操作通過調整相鄰節點的指針實現,并動態維護索引層數。


2、跳表模擬實現

? ? ? ? 2.1、跳表節點

? ? ? ? _val存儲每個節點存儲的值,_nextV指向下一個節點。

struct SkiplistNode
{int _val;vector<SkiplistNode*> _nextV;SkiplistNode(int val, int level):_val(val), _nextV(level, nullptr){}
};

? ? ? ? 2.2、跳表框架

? ? ? ? 對于跳表類的私有成員變量,有三個,_head是跳表的頭,這個節點不存儲值。_maxlevel存儲最高層高,_p是晉升概率,也就是層高加一的概率。后面會詳細說。

class Skiplist
{typedef SkiplistNode Node;
public:Skiplist(){srand(time(0));_head = new Node(-1, 1);}private:Node* _head;size_t _maxLevel = 32;double _p = 0.5;
};

? ? ? ? 2.3、 查詢

? ? ? ? 我們先來說查找,一會在介紹怎么添加節點、

? ? ? ? 對于查找的過程來說,我們從頭結點開始查找,如果下一個節點的值小于我們想要查找的target,我們就直接跳到下一個節點那。如果下一個節點的值大于我們想要查找的taget,我們就得下降層數。如果層數下降到-1(0層是最低層)也沒有找到target值,就跳出循環返回false。

????????我們拿一個跳表模擬一下查找的過程:

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;
}

????????2.4、 查找前置節點

? ? ? ? 首先我們先新建一個前置節點,然后把他初始化為level層,其實就是讓他的層數和跳表中最高節點的層數相等。我們把這個前置節點的存儲的值都初始化為_head。因為如果我們要查找的某個值都比跳表中的所有值都小,那么他的前置節點就是head。

? ? ? ? 在循環的過程中,每次調整level我們就記錄一下前置節點:

? ? ? ? 后序刪除和添加節點的時候我們都得先找到前置節點。

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){prevV[level] = cur;--level;}}return prevV;
}

? ? ? ? 2.5、添加節點

?????????再添加節點的操作中,因為我們要控制每個跳表節點的高度都是隨機的,所以說我們得先說一下生成隨機層高的函數:

? ? ? ? 隨機數生成部分:

??generator是靜態的默認隨機數引擎

? ? ? ?用當前系統時間作為種子(time_since_epoch().count()),確保每次程序運行時種子不同static關鍵字保證在整個程序運行期間只初始化一次

? ?distribution:靜態的均勻實數分布,范圍[0.0, 1.0)用于生成0到1之間的隨機浮點數。

? ? ? ? p是晉升概率,也就是我們層數++的概率。

????????節點層數至少為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)。

int RandomLevel()
{static std::default_random_engine generator(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;
}

? ? ? ? 接下來我們看添加節點操作。首先我們找到應該插入位置的前置節點,然后依次設置每一層的節點鏈接。

void add(int num)
{vector<Node*> prevV = FindPrevNode(num);int n = RandomLevel();Node* newnode = new Node(num, n);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;}
}

? ? ? ? 2.6、刪除節點

? ? ? ? 最后需要注意我們得調整一下頭結點的層數。

bool erase(int num)
{vector<Node*> prevV = FindPrevNode(num);if (prevV[0]->_nextV[0] == nullptr || prevV[0]->_nextV[0]->_val != num){//該節點不存在return false;}else{Node* del = prevV[0]->_nextV[0];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、跳表的性能分析

????????時間復雜度查詢、插入、刪除均為O(log n),依賴隨機層數分布。

????????空間復雜度平均O(n),索引節點數量約為n/2 + n/4 +... ≈ n。

? ? ? ? 時間復雜度都是由查詢操作卡著,而查詢操作其實是從上到下遍歷一遍。跳表的高度通常為O(log n),其中n是節點數量。

????????優勢實現簡單,無需復雜平衡操作;適合高并發場景(如Redis的有序集合)。

????????跳表作為一種高效動態數據結構,兼具實用性與靈活性,是算法設計中的經典案例。

? ? ? ? 好了,今天的內容就分享到這,我們下期再見!?

?

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

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

相關文章

從0設計一個短鏈接服務:如何實現盡可能短、可變長的短網址系統?

從 0 設計一個短鏈接服務&#xff1a;如何實現盡可能短、可變長的短網址系統&#xff1f; 在日常生活中&#xff0c;我們經常在短信、微博、廣告營銷中看到“短鏈接”&#xff0c;如&#xff1a; https://t.cn/EXaQ4xY https://bit.ly/3Yp9zJk相比冗長復雜的原始 URL&#xff0…

Microsoft Word 中 .doc 和 .docx 的區別

Microsoft Word 中 .doc 和 .docx 的區別 解釋 Microsoft Word 中 .doc 和 .docx 文件格式的區別。這些格式都是 Word 處理文檔的標準&#xff0c;但它們在結構、兼容性和功能上存在顯著差異。下面我將詳細說明。 1. 基本定義 .doc&#xff1a;這是 Microsoft Word 的舊格式&am…

Springboot aop面向切面編程

aop:面向切面編程&#xff0c;理解在一個流程中插入一個切面&#xff0c;這樣切面方法會在指定位置執行能無影響的在某些方法前或者后插入一些動作springboot使用1.引入依賴<dependency><groupId>org.springframework.boot</groupId><artifactId>sprin…

手機識別數據集,2628張原始圖片,支持yolo,coco json,pasical voc xml等格式的標注

本文提供手機識別數據集&#xff0c;2628張原始圖片&#xff0c;支持yolo&#xff0c;coco json,pasical voc xml等格式的標注的數據集下載&#xff0c;下載地址在文末手機識別數據集簡介手機識別數據集通常用于訓練和評估機器學習模型&#xff0c;以識別不同手機品牌、型號或功…

ollama - sqlcoder模型:面向提示詞編程(根據用戶信息生成sql語句并執行返回結果)

https://ollama.ac.cn/library/sqlcoderhttps://blog.csdn.net/hzether/article/details/143816042import ollama import sqlite3 import json from contextlib import closingdef generate_and_execute_sql(question: str, db_path: str) -> dict:# 1. 生成 SQL 查詢語句pr…

C語言,結構體指針案例

案例一&#xff1a; #include <stdio.h> #include <stdbool.h> #include <string.h> // 添加string.h頭文件用于strcpy //結構體指針//方式 1 : 先定義結構體 struct Dog {char *name;int age;char weight; };//方式 1 : char *get_dog_info(struct Dog do…

Vue 3 中父子組件雙向綁定的 4 種方式

&#x1f501; Vue 3 中父子組件雙向綁定的 4 種方式 整理不易&#xff0c;點贊 收藏 關注&#xff0c;助你組件通信不再混亂&#xff01;? 場景說明 父組件希望將某個值傳遞給子組件&#xff0c;同時希望子組件能夠修改這個值&#xff08;實現“綁定 反向更新”&#xff0…

阻有形,容無聲——STA 簽核之RC Corner

RC corner&#xff0c;RC指的是gate跟network的寄生參數&#xff0c;寄生參數抽取工具&#xff08;比如Starrc&#xff09;根據電路的物理信息&#xff0c;抽取出電路的電阻電容值&#xff0c;再以寄生參數文件&#xff08;Spef&#xff09;輸入給STA工具&#xff08;PT&#x…

多代理系統(multi-agent)框架深度解析:架構、特性與未來

在人工智能技術迭代的浪潮中&#xff0c;多代理系統&#xff08;Multi-Agent System&#xff09;正從實驗室走向產業應用的核心舞臺。這一技術范式的崛起源于三大驅動力&#xff1a;大模型能力的指數級提升、復雜任務分解的需求爆發&#xff0c;以及傳統單體智能架構的局限性日…

【Redis】黑馬點評筆記:使用redis解決各種分布式/并發問題

1、系統架構2、基于session登錄用戶的 session 是由服務器&#xff08;如 Tomcat&#xff09;自動管理和維護的&#xff0c;每個用戶在訪問 Web 應用時都會擁有一個獨立的 session 對象。這個對象是通過瀏覽器和服務器之間的 HTTP 協議自動綁定的。1. 如何區分不同用戶的 Sessi…

Javaweb- 11 MVC架構模式

MVC&#xff08;Model View Controller&#xff09; 是軟件工程中一種軟件架構模式&#xff0c;它把軟件系統分為模型&#xff0c;視圖&#xff0c;控制器&#xff0c;三個基本部分。用一種業務邏輯&#xff0c;數據&#xff0c;界面顯示分離的方法組織代碼&#xff0c;將業務邏…

【電腦】主板的基礎知識

主板&#xff08;Motherboard&#xff09;是計算機的核心組件之一&#xff0c;它將所有其他硬件部件連接在一起并協調它們的工作。以下是關于主板的詳細知識&#xff1a;1. 架構組成一個典型的主板通常由以下幾個主要部分構成&#xff1a;芯片組&#xff08;Chipset&#xff09…

【飛算JavaAI】一站式智能開發,驅動Java開發全流程革新

【作者主頁】Francek Chen 【專欄介紹】???人工智能與大模型應用??? 人工智能&#xff08;AI&#xff09;通過算法模擬人類智能&#xff0c;利用機器學習、深度學習等技術驅動醫療、金融等領域的智能化。大模型是千億參數的深度神經網絡&#xff08;如ChatGPT&#xff09…

STM32中的RTC(實時時鐘)詳解

前言&#xff1a;為什么需要RTC&#xff1f; 在嵌入式系統中&#xff0c;時間記錄是一項基礎且關鍵的功能。想象一下&#xff1a;智能家居設備需要按時間觸發開關燈&#xff0c;工業儀表需要記錄傳感器數據的采集時刻&#xff0c;物聯網終端需要同步服務器時間戳……這些場景都…

Python技巧記錄

空格拼接數組格式化顯示 一維數組 arr [1, 2, 3, 4, 5] print( .join(map(str, arr))) # 直接轉換并連接二維數組 for row in arr:print( .join(map(str, row)))for row in arr: 此循環會遍歷矩陣arr中的每一行。這里的arr是一個二維列表&#xff0c;每一行代表一個子列表。m…

next.js打包后的前端資源如何進行部署和訪問,為什么沒有index.html

在 Next.js 項目中&#xff0c;打包后的部署方式和傳統單頁應用&#xff08;SPA&#xff09;有所不同&#xff0c;尤其是沒有直接生成 index.html 這一點。以下是詳細解釋和部署指南&#xff1a;為什么沒有 index.html 文件&#xff1f; Next.js 采用 混合渲染策略&#xff0c;…

Qt+FFmpeg網絡視頻流播放

init 函數用于初始化 FFmpeg&#xff0c;包括設置參數、打開輸入、初始化視頻和音頻等。initOption 函數用于設置 FFmpeg 的參數選項。bool FFmpegThread::init() {if (url.isEmpty()) {return false;}//判斷該攝像機是否能聯通if (checkConn && isRtsp) {if (!checkUr…

【SpringBoot】Spring Boot 高并發優化終極指南,涵蓋線程模型、JVM 調優、數據庫訪問、緩存策略等 15+ 核心模塊

Spring Boot 高并發優化終極指南&#xff0c;涵蓋線程模型、JVM 調優、數據庫訪問、緩存策略等 15 核心模塊一、線程模型深度調優&#xff08;核心瓶頸突破&#xff09;1. Tomcat 線程池原子級配置2. 異步任務線程池隔離策略二、JVM 層終極調參&#xff08;G1GC 深度優化&#…

linux(CentOS-7-x86_64:NAT模式下解決yum無法使用:更新yum源的詳細操作步驟2025)

目錄 一、CentOS-7-x86_64的NAT模式下解決yum無法使用。&#xff08;更新可用的yum&#xff09; &#xff08;1&#xff09;首先保證能夠ping通&#xff0c;也就是NAT模式下虛擬機有網絡。 &#xff08;2&#xff09;錯誤&#xff1a;無法使用yum。比如我現在無法yum search if…

C++11的整理筆記

Lambda 表達式Lambda 表達式是 C11 引入的一種強大的功能&#xff0c;它允許你在代碼中直接定義匿名函數對象。Lambda 表達式可以捕獲上下文中的變量&#xff0c;并在需要時使用它們。它們通常用于簡化代碼&#xff0c;尤其是那些需要傳遞函數對象作為參數的場景&#xff08;如…