日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

17 | 跳表:为什么Redis一定要用跳表来实现有序集合?

發布時間:2023/12/10 数据库 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 17 | 跳表:为什么Redis一定要用跳表来实现有序集合? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

問題:如果數據存儲在鏈表中,就真的沒法用二分查找算法了嗎?可以對鏈表進行“改造”,就可以支持類似“二分”的查找算法。

跳表

定義:對鏈表經過改造之后的數據結構叫做跳表(Skip list),是一種各方面性能都比較優秀的動態數據結構,

特點:

  • 可以支持快速地插入、刪除、查找操作,甚至可以替代紅黑樹(Red-black tree)。
  • Redis 中的有序集合(Sorted Set)就是用跳表來實現的。類似的紅黑樹也可以實現快速地插入、刪除和查找操作

如何理解跳表?

1、原始的鏈表:查找效率很低,時間復雜度會很高,是 O(n)

2、改造:對鏈表建立一級“索引”,每兩個結點提取一個結點到上一級,把抽出來的那一級叫做索引或索引層。圖中的 down 表示 down 指針,指向下一級結點

此時查找結點16可以先遍歷第一級索引,找到結點13發現下一個結點17>16,然后我們通過索引層結點的 down 指針,下降到原始鏈表這一層,繼續遍歷。只需要再遍歷 2 個結點,就可以找到值等于 16的結點。依次類推,繼續向上建立多級索引:

這種鏈表加多級索引的結構,就是跳表。當鏈表的長度 n 比較大時,比如 1000、10000 的時候,在構建索引之后,查找效率的提升就會非常明顯。

跳表查詢的時間復雜度

1、如果鏈表里有 n 個結點,會有多少級索引呢?每兩個結點會抽出一個結點作為上一級索引的結點,那第一級索引的結點個數大約就是 n/2,第二級索引的結點個數大約就是 n/4,第三級索引的結點個數大約就是 n/8,依次類推,也就是說,第 k 級索引的結點個數是第 k-1 級索引的結點個數的 1/2,那第 k級索引結點的個數就是 n/(2k)。

2、假設索引有 h 級,最高級的索引有 2 個結點。通過上面的公式可以得到 n/(2h)=2,從而求得 h=log2n-1。如果包含原始鏈表這一層,整個跳表的高度就是 log2n。在跳表中查詢某個數據的時候,如果每一層都要遍歷 m 個結點,那在跳表中查詢一個數據的時間復雜度就是 O(m*logn)。

3、按照前面這種索引結構,我們每一級索引都最多只需要遍歷 3 個結點,也就是說 m=3

解釋:假設我們要查找的數據是 x,在第 k 級索引中,我們遍歷到 y 結點之后,發現 x 大于 y,小于后面的結點 z,所以我們通過 y 的 down 指針,從第 k 級索引下降到第 k-1 級索引。在第 k-1 級索引中,y 和 z 之間只有 3 個結點(包含 y 和 z),所以,我們在 K-1 級索引中最多只需要遍歷 3 個結點,依次類推,每一級索引都最多只需要遍歷 3 個結點。

4、跳表中查詢任意數據的時間復雜度就是 O(logn)。這個查找的時間復雜度跟二分查找是一樣的。基于單鏈表實現了二分查找,不過,這種查詢效率的提升前提是建立了很多級索引,使用到了空間換時間的設計思路。

跳表查詢的空間復雜度

假設原始鏈表大小為 n,那第一級索引大約有 n/2 個結點,第二級索引大約有 n/4 個結點,以此類推,每上升一級就減少一半,直到剩下 2 個結點。這幾級索引的結點總和就是 n/2+n/4+n/8…+8+4+2=n-2。所以,跳表的空間復雜度是 O(n)。也就是說,如果將包含 n 個結點的單鏈表構造成跳表,我們需要額外再用接近 n 個結點的存儲空間。那如何降低索引占用的內存空間呢?

如果我們每三個結點或五個結點,抽一個結點到上級索引,就可以減少空間占用。實際上,在軟件開發中,我們不必太在意索引占用的額外空間。在講數據結構和算法時,我們習慣性地把要處理的數據看成整數,但是在實際的軟件開發中,原始鏈表中存儲的有可能是很大的對象,而索引結點只需要存儲關鍵值和幾個指針,并不需要存儲對象,所以當對象比索引結點大很多時,那索引占用的額外空間就可以忽略了

高效的動態插入和刪除

1、對于跳表來說,我們講過查找某個結點的時間復雜度是 O(logn),所以這里查找某個數據應該插入的位置,方法也是類似的,時間復雜度也是 O(logn)

2、跳表的刪除操作除了要刪除原始鏈表中的結點,還要刪除索引中的。因為單鏈表中的刪除操作需要拿到要刪除結點的前驅結點,然后通過指針操作完成刪除。所以在查找要刪除的結點的時候,一定要獲取前驅結點。當然,如果我們用的是雙向鏈表,就不需要考慮這個問題了

跳表索引動態更新

當我們不停地往跳表中插入數據時,如果我們不更新索引,就有可能出現某 2 個索引結點之間數據非常多的情況。極端情況下,跳表還會退化成單鏈表。

作為一種動態數據結構,需要某種手段來維護索引與原始鏈表大小之間的平衡。如果鏈表中結點多了,索引結點就相應地增加一些,避免復雜度退化,以及查找、插入、刪除操作性能下降。紅黑樹、AVL 樹這樣平衡二叉樹是通過左右旋的方式保持左右子樹的大小平衡,而跳表是通過隨機函數來維護前面提到的“平衡性”

1、當我們往跳表中插入數據的時候,我們可以選擇同時將這個數據插入到部分索引層中。索引在垂直方向上也必須是一個完整的鏈表,如果一個數據在某一層索引中,那么它必須同時存在于所有的下級索引中

2、通過一個隨機函數,來決定將這個結點插入到哪幾級索引中。隨機函數生成了值 K,那我們就將這個結點添加到第一級到第 K 級這 K 級索引中

3、隨機函數的選擇很有講究,從概率上來講,能夠保證跳表的索引大小和數據大小平衡性,不至于性能過度退化

Redis 為什么用跳表來實現有序集合?

Redis 中的有序集合支持的核心操作主要有下面這幾個:

  • 插入一個數據;
  • 刪除一個數據;
  • 查找一個數據;
  • 按照區間查找數據(比如查找值在[100, 356]之間的數據);
  • 迭代輸出有序序列。

原因:

1、只有按照區間來查找數據這個操作,紅黑樹的效率沒有跳表高,其他的操作紅黑樹都可以完成。按照區間查找數據這個操作,跳表可以做到 O(logn) 的時間復雜度定位區間的起點,然后在原始鏈表中順序往后遍歷就可以了。這樣做非常高效。

2、相對于紅黑樹的代碼實現,跳表代碼實現更容易。雖然跳表的實現也不簡單,但比起紅黑樹來說還是好懂、好寫多了,而簡單就意味著可讀性好,不容易出錯。

3、跳表更加靈活,它可以通過改變索引構建策略,有效平衡執行效率和內存消耗

總結

以上是生活随笔為你收集整理的17 | 跳表:为什么Redis一定要用跳表来实现有序集合?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。