資料結構和演算法之——跳錶
之前我們知道,二分查詢依賴陣列的隨機訪問,所以只能用陣列來實現。如果資料儲存在連結串列中,就真的沒法用二分查詢了嗎?而實際上,我們只需要對連結串列稍加改造,就可以實現類似“二分”的查詢演算法,這種改造之後的資料結構叫作跳錶(Skip List)。
1. 何為跳錶?
對於一個單連結串列,即使連結串列是有序的,如果我們想要在其中查詢某個資料,也只能從頭到尾遍歷連結串列,這樣效率自然就會很低。
假如我們對連結串列每兩個結點提取一個結點到上一級,然後建立一個索引指向原始結點,如下圖所示。
這時候,我們要查詢某一個資料的時候,就可以先在索引裡面查詢出一個大的範圍,然後再下降到原始連結串列中精確查詢。
比如,我們要查詢 16,我們發現 16 位於 13 和 17 之間,這時候,我們就從 13 的地方下降到原始連結串列,然後再往後查詢。原來我們查詢 16,需要遍歷 10 個結點,現在只需要遍歷 7 個結點。
我們發現,加一層索引後,查詢一個結點需要遍歷的次數減少了,也就是查詢效率提高了。
那麼我們再多加一級索引呢?效果會不會有更大提升?
這一次,我們只需要遍歷 6 個結點了。
資料量不大的時候這種方法可能效率提高得還不是很明顯,下面看一個包含 64 個結點的例子,這次我們建立了五級索引。
查詢 62 的時候原來需要遍歷 62 次,現在只需要 11 次即可。針對連結串列長度比較大的時候,構建索引查詢效率的提升就會非常明顯。
2. 跳錶查詢的分析?
如果連結串列中總共有 個結點,那麼第一級索引就有 個結點,第二級索引就有 個結點,以此類推,那麼第 級索引就有 個結點。如果最高階索引有 2 個結點,那總的索引級數 ,如果我們算上原始連結串列的話,那也就是總共有 級。
在第 級索引中,假設我們要查詢的資料為 ,當我們查詢到 結點時,發現 時此時我們就要下降到 級索引繼續查詢。在第 級索引中, 和 之間只有三個結點,因此,我們最多隻需要查詢 3 個結點。以此類推,每一級的索引最多都只需要遍歷 3 個結點。
而總的級別數為 ,因此查詢的時間複雜度就為 。跳錶查詢的時間複雜度和二分查詢一樣,但這其實是以空間來換時間的設計思路。
跳錶的所有額外索引結點總數為 ,所以跳錶的空間複雜度為 。
但如果我們每三個結點建立一個索引,這時候額外需要的結點總數為 ,雖然空間複雜度依然為 ,但減少了一半的索引節點儲存空間。
實際上,在實際開發中,原始連結串列中儲存的可能是很大的物件,而索引結點只需要儲存關鍵值和幾個指標,其額外佔用的空間可以被忽略掉。
3. 跳錶高效的動態插入和刪除?
在連結串列中,如果我們知道要插入資料的位置,那麼插入的時間複雜度就為 。在跳錶中,查詢的時間複雜度為 ,因此,動態插入資料的時間複雜度也就是 了。
從連結串列中刪除結點的時候,如果結點在索引中也有出現,那麼我們除了要刪除原始連結串列中的結點,還要刪除索引中的。
當我們不停地往跳錶中插入資料的時候,如果我們不更新索引,就有可能出現某兩個結點之間資料非常多的情況。極端情況下,跳錶還會退化為單連結串列。
因此,我們需要某種手段來維護索引與原始連結串列大小之間的平衡,也就是說,如果連結串列結點變多了,索引值就相應地增加一些。
當我們往跳錶中插入資料的時候,我們可以選擇同時也將這個資料插入到部分索引層中。而插入到哪些索引層中,則由一個隨機函式生成一個隨機數字來決定。如果這個數字為 K,那我們就將資料插入到第一級到第 K 級索引中。
4. 為什麼 Redis 要用跳錶來實現有序集合而不是紅黑樹?
Redis 中的有序集合支援的核心操作主要有以下幾個:
- 插入一個資料
- 刪除一個資料
- 查詢一個資料
- 按照區間查詢資料
- 迭代輸出有序序列
其中,插入、刪除、查詢以及迭代輸出有序序列這幾個操作,紅黑樹也可以完成,時間複雜度和跳錶是一樣的。
但是,按照區間查詢資料這個操作,紅黑樹的效率沒有跳錶高。跳錶可以在 時間複雜度定位區間的起點,然後在原始連結串列中順序向後查詢就可以了,這樣非常高效。
此外,相比於紅黑樹,跳錶還具有程式碼更容易實現、可讀性好、不容易出錯、更加靈活等優點,因此 Redis 用跳錶來實現有序集合。
獲取更多精彩,請關注「seniusen」!
相關文章
- 資料結構之跳錶資料結構
- 資料結構與演算法-跳錶《八》資料結構演算法
- 資料結構與演算法整理總結---跳錶資料結構演算法
- 自己動手實現java資料結構(九) 跳錶Java資料結構
- 資料結構和演算法之-列表資料結構演算法
- 資料結構與演算法---跳躍表資料結構演算法
- 跳躍表資料結構與演算法分析資料結構演算法
- 資料結構和演算法資料結構演算法
- 資料結構與演算法之線性結構資料結構演算法
- 圖解Redis之資料結構篇——跳躍表圖解Redis資料結構
- 7.跳錶:各方面都非常優秀的動態資料結構資料結構
- 資料結構和演算法-堆資料結構演算法
- JavaScript資料結構和演算法JavaScript資料結構演算法
- 聊聊資料結構和演算法資料結構演算法
- python演算法與資料結構-演算法和資料結構介紹(31)Python演算法資料結構
- 資料結構:跳躍連結串列資料結構
- 資料結構和演算法總結--棧資料結構演算法
- 資料結構與演算法之美資料結構演算法
- 演算法與資料結構之集合演算法資料結構
- 資料結構與演算法之排序資料結構演算法排序
- 《資料結構與演算法之美》為什麼要學習資料結構和演算法 (讀後感)資料結構演算法
- 資料結構(一)--- 跳躍表資料結構
- Redis資料結構—跳躍表Redis資料結構
- 資料結構和演算法之——二分查詢下資料結構演算法
- 資料結構和演算法之——二分查詢上資料結構演算法
- [面試專題]資料結構和演算法-JS之魂面試資料結構演算法JS
- Java的資料結構和演算法Java資料結構演算法
- JavaScript 的資料結構和演算法JavaScript資料結構演算法
- 資料結構和演算法:遞迴資料結構演算法遞迴
- python之資料結構與演算法分析Python資料結構演算法
- 資料結構與演算法之快速排序資料結構演算法排序
- iOS標準庫中常用資料結構和演算法之KV資料庫iOS資料結構演算法資料庫
- iOS標準庫中常用資料結構和演算法之連結串列iOS資料結構演算法
- 資料結構與演算法-資料結構(棧)資料結構演算法
- 資料結構和演算法面試題系列—排序演算法之快速排序資料結構演算法面試題排序
- 04 Javascript資料結構與演算法 之 字典和雜湊表JavaScript資料結構演算法
- 資料結構與演算法基礎之指標和陣列資料結構演算法指標陣列
- 每週一練 之 資料結構與演算法(Dictionary 和 HashTable)資料結構演算法