跳表 - 秒懂


文章很長,建議收藏起來,慢慢讀! 瘋狂創客圈為小伙伴奉上以下珍貴的學習資源:


JUC 高並發工具類(3文章)與高並發容器類(N文章) :

1.1 跳表圖解

跳表,是基於鏈表實現的一種類似“二分”的算法。它可以快速的實現增,刪,改,查操作。

我們先來看一下單向鏈表如何實現查找:

在這里插入圖片描述

鏈表,相信大家都不陌生,維護一個有序的鏈表是一件非常簡單的事情,我們都知道,在一個有序的鏈表里面,查找某個數據的時候需要的時間復雜度為O(n).

怎么提高查詢效率呢?如果我們給該單鏈表加一級索引,將會改善查詢效率。

在這里插入圖片描述

如圖所示,當我們每隔一個節點就提取出來一個元素到上一層,把這一層稱作索引,上層的索引節點都加上一個down指針指向原始節點。

當我們查找元素11的時候,單鏈表需要比較6次,而加過索引的兩級鏈表只需要比較4次。當數據量增大到一定程度的時候,效率將會有顯著的提升。

如果我們再加多幾級索引的話,效率將會進一步提升。這種鏈表加多級索引的結構,就叫做跳表。
在這里插入圖片描述

跳表的查詢時間復雜度可以達到O(logn)

1.2 為什么Redis的有序集合SortedSet要使用跳表實現

跳表就是這樣的一種數據結構,結點是跳過一部分的,從而加快了查詢的速度。跳表跟紅黑樹又有什么差別呢?既然兩者的算法復雜度差不多,為什么Redis的有序集合SortedSet要使用跳表實現,而不使用紅黑樹呢?

Redis 中的有序集合是通過跳表來實現的,嚴格點講,其實還用到了散列表。

如果你去查看 Redis 的開發手冊,就會發現,Redis 中的有序集合支持的核心操作主要有下面這幾個:

  • 插入一個數據;

  • 刪除一個數據;

  • 查找一個數據;

  • 按照區間查找數據(比如查找值在 [100, 356] 之間的數據);

  • 迭代輸出有序序列。

其中,插入、刪除、查找以及迭代輸出有序序列這幾個操作,紅黑樹也可以完成,時間復雜度跟跳表是一樣的。但是,按照區間來查找數據這個操作,紅黑樹的效率沒有跳表高。對於按照區間查找數據這個操作,跳表可以做到 O(logn) 的時間復雜度定位區間的起點,然后在原始鏈表中順序往后遍歷就可以了。這樣做非常高效。

當然,Redis之所以用跳表來實現有序集合,還有其他原因,比如,跳表更容易代碼實現。雖然跳表的實現也不簡單,但比起紅黑樹來說還是好懂、好寫多了,而簡單就意味着可讀性好,不容易出錯。還有,跳表更加靈活,它可以通過改變索引構建策略,有效平衡執行效率和內存消耗

1.3 跳表的查詢操作

假如我們要查詢11,那么我們從最上層出發,發現下一個是5,再下一個是13,已經大於11,所以進入下一層,下一層的一個是9,查找下一個,下一個又是13,再次進入下一層。最終找到11。

在這里插入圖片描述

是不是非常的簡單?我們可以把查找的過程總結為一條二元表達式(下一個是否大於結果?下一個:下一層)。理解跳表的查詢過程非常重要,試試看查詢其他數字,只要你理解了查詢,后面兩種都非常簡單。

1.4 跳表的插入

接下來看插入,我們先看理想的跳躍表結構,L2層的元素個數是L1層元素個數的1/2,L3層的元素個數是L2層的元素個數的1/2,以此類推。從這里,我們可以想到,只要在插入時盡量保證上一層的元素個數是下一層元素的1/2,我們的跳躍表就能成為理想的跳躍表。那么怎么樣才能在插入時保證上一層元素個數是下一層元素個數的1/2呢?很簡單,拋硬幣就能解決了!

假設元素X要插入跳躍表,很顯然,L1層肯定要插入X。那么L2層要不要插入X呢?我們希望上層元素個數是下層元素個數的1/2,所以我們有1/2的概率希望X插入L2層,那么拋一下硬幣吧,正面就插入,反面就不插入。那么L3到底要不要插入X呢?相對於L2層,我們還是希望1/2的概率插入,那么繼續拋硬幣吧!以此類推,元素X插入第n層的概率是(1/2)的n次。這樣,我們能在跳躍表中插入一個元素了。

跳躍表的初試狀態如下圖,表中沒有一個元素:

在這里插入圖片描述

如果我們要插入元素2,首先是在底部插入元素2,如下圖:

在這里插入圖片描述

然后我們拋硬幣,結果是正面,那么我們要將2插入到L2層,如下圖

在這里插入圖片描述

繼續拋硬幣,結果是反面,那么元素2的插入操作就停止了,插入后的表結構就是上圖所示。接下來,我們插入一個新元素33,跟元素2的插入一樣,現在L1層插入33,如下圖:

在這里插入圖片描述

然后拋硬幣,結果是反面,那么元素33的插入操作就結束了,插入后的表結構就是上圖所示。接下來,我們插入一個新元素55,首先在L1插入55,插入后如下圖:

在這里插入圖片描述

然后拋硬幣,結果是正面,那么L2層需要插入55,如下圖:

在這里插入圖片描述

繼續拋硬幣,結果又是正面,那么L3層需要插入55,如下圖:

在這里插入圖片描述

繼續拋硬幣,結果又是正面,那么要在L4插入55,結果如下圖:

在這里插入圖片描述

繼續拋硬幣,結果是反面,那么55的插入結束,表結構就如上圖所示。

當然,不可能無限的進行層數增長。除了根據一種隨機算法得到的層數之外,為了不讓層數過大,還會有一個最大層數MAX_LEVEL限制,隨機算法生成的層數不得大於該值。

以此類推,我們插入剩余的元素。當然因為規模小,結果很可能不是一個理想的跳躍表。但是如果元素個數n的規模很大,學過概率論的同學都知道,最終的表結構肯定非常接近於理想跳躍表。

當然,這樣的分析在感性上是很直接的,但是時間復雜度的證明實在復雜,在此我就不深究了,感興趣的可以去看關於跳躍表的paper。

1.5 跳表的刪除

再討論刪除,刪除操作沒什么講的,直接刪除元素,然后調整一下刪除元素后的指針即可。跟普通的鏈表刪除操作完全一樣。

插入和刪除的時間復雜度就是查詢元素插入位置的時間復雜度,這不難理解,所以是O(logn)。


回到◀瘋狂創客圈

瘋狂創客圈 - Java高並發研習社群,為大家開啟大廠之門


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM