跳錶 - 秒懂


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

1.1 跳錶圖解

跳錶,是基於鏈表實現的一種相似「二分」的算法。它能夠快速的實現增,刪,改,查操做。算法

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

在這裏插入圖片描述

鏈表,相信你們都不陌生,維護一個有序的鏈表是一件很是簡單的事情,咱們都知道,在一個有序的鏈表裏面,查找某個數據的時候須要的時間複雜度爲O(n).數據結構

怎麼提升查詢效率呢?若是咱們給該單鏈表加一級索引,將會改善查詢效率。併發

在這裏插入圖片描述

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

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

若是咱們再加多幾級索引的話,效率將會進一步提高。這種鏈表加多級索引的結構,就叫作跳錶。
在這裏插入圖片描述3d

跳錶的查詢時間複雜度能夠達到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高併發研習社羣,爲你們開啓大廠之門

相關文章
相關標籤/搜索