1. 程式人生 > >資料結構和演算法之——跳錶

資料結構和演算法之——跳錶

之前我們知道,二分查詢依賴陣列的隨機訪問,所以只能用陣列來實現。如果資料儲存在連結串列中,就真的沒法用二分查找了嗎?而實際上,我們只需要對連結串列稍加改造,就可以實現類似“二分”的查詢演算法,這種改造之後的資料結構叫作跳錶(Skip List)

1. 何為跳錶?

對於一個單鏈表,即使連結串列是有序的,如果我們想要在其中查詢某個資料,也只能從頭到尾遍歷連結串列,這樣效率自然就會很低。

假如我們對連結串列每兩個結點提取一個結點到上一級,然後建立一個索引指向原始結點,如下圖所示。

這時候,我們要查詢某一個數據的時候,就可以先在索引裡面查找出一個大的範圍,然後再下降到原始連結串列中精確查詢。

比如,我們要查詢 16,我們發現 16 位於 13 和 17 之間,這時候,我們就從 13 的地方下降到原始連結串列,然後再往後查詢。原來我們查詢 16,需要遍歷 10 個結點,現在只需要遍歷 7 個結點。

我們發現,加一層索引後,查詢一個結點需要遍歷的次數減少了,也就是查詢效率提高了

那麼我們再多加一級索引呢?效果會不會有更大提升?

這一次,我們只需要遍歷 6 個結點了。

資料量不大的時候這種方法可能效率提高得還不是很明顯,下面看一個包含 64 個結點的例子,這次我們建立了五級索引。

查詢 62 的時候原來需要遍歷 62 次,現在只需要 11 次即可。針對連結串列長度比較大的時候,構建索引查詢效率的提升就會非常明顯

2. 跳錶查詢的分析?

如果連結串列中總共有 nn 個結點,那麼第一級索引就有 n2\frac{n}{2} 個結點,第二級索引就有 n4\frac{n}{4} 個結點,以此類推,那麼第 kk 級索引就有 n2k\frac{n}{2^k} 個結點。如果最高階索引有 2 個結點,那總的索引級數 k=log2n1k = log_2n - 1,如果我們算上原始連結串列的話,那也就是總共有 log2nlog_2n 級。

在第 kk 級索引中,假設我們要查詢的資料為 xx,當我們查詢到 yy 結點時,發現 y<x<

zy < x < z 時此時我們就要下降到 k1k-1 級索引繼續查詢。在第 k1k-1 級索引中,yyzz 之間只有三個結點,因此,我們最多隻需要查詢 3 個結點。以此類推,每一級的索引最多都只需要遍歷 3 個結點

而總的級別數為 log2nlog_2n,因此查詢的時間複雜度就為 3log2n=logn3* log_2n = logn。跳錶查詢的時間複雜度和二分查詢一樣,但這其實是以空間來換時間的設計思路。

跳錶的所有額外索引結點總數為 n2+n4+n8+...+4+2=n2\frac{n}{2} + \frac{n}{4} + \frac{n}{8} + ... + 4 + 2 = n-2,所以跳錶的空間複雜度為 O(n)O(n)

但如果我們每三個結點建立一個索引,這時候額外需要的結點總數為 n2\frac{n}{2},雖然空間複雜度依然為 O(n)O(n),但減少了一半的索引節點儲存空間。

實際上,在實際開發中,原始連結串列中儲存的可能是很大的物件,而索引結點只需要儲存關鍵值和幾個指標,其額外佔用的空間可以被忽略掉

3. 跳錶高效的動態插入和刪除?

在連結串列中,如果我們知道要插入資料的位置,那麼插入的時間複雜度就為 O(1)O(1)。在跳錶中,查詢的時間複雜度為 O(logn)O(logn),因此,動態插入資料的時間複雜度也就是 O(logn)O(logn) 了。

從連結串列中刪除結點的時候,如果結點在索引中也有出現,那麼我們除了要刪除原始連結串列中的結點,還要刪除索引中的。

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

因此,我們需要某種手段來維護索引與原始連結串列大小之間的平衡,也就是說,如果連結串列結點變多了,索引值就相應地增加一些

當我們往跳錶中插入資料的時候,我們可以選擇同時也將這個資料插入到部分索引層中。而插入到哪些索引層中,則由一個隨機函式生成一個隨機數字來決定。如果這個數字為 K,那我們就將資料插入到第一級到第 K 級索引中。

4. 為什麼 Redis 要用跳錶來實現有序集合而不是紅黑樹?

Redis 中的有序集合支援的核心操作主要有以下幾個:

  • 插入一個數據
  • 刪除一個數據
  • 查詢一個數據
  • 按照區間查詢資料
  • 迭代輸出有序序列

其中,插入、刪除、查詢以及迭代輸出有序序列這幾個操作,紅黑樹也可以完成,時間複雜度和跳錶是一樣的。

但是,按照區間查詢資料這個操作,紅黑樹的效率沒有跳錶高。跳錶可以在 O(logn)O(logn) 時間複雜度定位區間的起點,然後在原始連結串列中順序向後查詢就可以了,這樣非常高效。

此外,相比於紅黑樹,跳錶還具有程式碼更容易實現、可讀性好、不容易出錯、更加靈活等優點,因此 Redis 用跳錶來實現有序集合。

獲取更多精彩,請關注「seniusen」!

相關推薦

資料結構演算法——

之前我們知道,二分查詢依賴陣列的隨機訪問,所以只能用陣列來實現。如果資料儲存在連結串列中,就真的沒法用二分查找了嗎?而實際上,我們只需要對連結串列稍加改造,就可以實現類似“二分”的查詢演算法,這種改造之後的資料結構叫作跳錶(Skip List)。 1. 何為

資料結構演算法陣列奇數、偶數分離

        今日,博主在面試一家外企的時候,要求白板寫程式。其中就有一道演算法設計題目,下面就來分享一下這道題的演算法思路和相關示例程式碼。         題目:要求將一個整形陣列中的奇數和偶數進行分離,偶數在

資料結構演算法——散列表下

散列表和連結串列經常組合起來使用,但它們是如何組合起來使用的,為什麼它們會經常一塊使用呢? 1. LRU 快取淘汰演算法? 基於連結串列實現 LRU 快取淘汰演算法的原理是這樣的:我們維護一個有序單鏈表,越靠近連結串列頭部的結點是越早訪問的。當有一個新的資料被訪問時,我們從連結串列頭開始順序遍歷

資料結構演算法棧排序

題目:兩組數,左邊為已升序排列稱為S,右邊的未排序稱為R,在空間複雜度為O(1)的情況下將所有數排序 思路:兩組數為兩個棧,S是可以為空的。迴圈以下基本操作,直到R為空:      彈出R的棧頂,用變數T儲存,由於S為升序排列,所以棧頂為最大,那麼只要S的     &nbs

資料結構演算法——散列表中

散列表的查詢效率並不能籠統地說成是 ,它和雜湊函式、裝載因子、雜湊衝突等都有關係。如果雜湊函式設計得不好,或者裝載因子過高,都可能會導致雜湊衝突發生的概率升高,查詢效率下降。 1. 如何設計雜湊函式? 雜湊函式設計的好壞,決定了雜湊衝突發生的概率,也直接決定了散列表的效能。那什麼才是好的雜湊函式

資料結構演算法學習--

跳錶 Skip List是一種隨機化的資料結構,基於並聯的連結串列,其效率可比擬於二叉查詢樹(對於大多數操作需要O(log n)平均時間)。基本上,跳躍列表是對有序的連結串列增加上附加的前進連結,增加是以隨機化的方式進行的,所以在列表中的查詢可以快速的跳過部分列表(因此得名)。所有操作都

資料結構演算法——散列表上

散列表的英文叫 “Hash Table”,我們也叫它 “雜湊表” 或者 “Hash 表”。 1. 雜湊思想? 散列表用的是陣列支援按照下標隨機訪問資料的特性,所以散列表其實就是陣列的一種擴充套件,由陣列演化而來。 假如我們有 100 名選手參加運動會,參賽號

【專欄】資料結構演算法美-為什麼很多程式語言中的陣列都是從 0 開始的

學習筆記 陣列的特徵 1.線性表 資料排成像一條線一樣的結構,資料之間只是簡單的前後關係。除了陣列是一種線性表結構外,連結串列、佇列和棧也是。與之對應的像二叉樹、堆、圖等就是非線性表。 2.使用連續

資料結構演算法美-二叉樹(上)

學習筆記 “樹”這種資料結構的形態特徵 包括有哪些命名節點和它們的概念,這些節點是根節點,葉子節點,父節點,子節點,兄弟節點等;以及相關節點關係的建立,這些關係是父子關係和兄弟關係 “樹"這種資

資料結構演算法-列表

列表的實現 (function(window) { var win = window, _restore = { listSize: function() { return this.restore.length; }, length: function() {

再也不怕資料結構演算法開篇

## 為什麼要學習演算法和資料結構 >演算法和資料結構是程式設計師的基本內功,基本內功修煉不好,以後修煉一些招式,如設計模式、架構,新的技術熱點如區塊鏈,新的技術語言go等,都會感覺非常吃力。 喜歡看武俠小說的知道,張無忌正是因為內功精純,再加乾坤大挪移加持,學習任何武功招式都如探囊取物,短時間內即可

資料結構演算法美專欄學習筆記-

跳錶的概念 對連結串列建立n級索引,例如每兩個結點提取一個節點到上一層,稱之為索引層。 圖中的down表示down指標,指向下一級結點   跳錶的時間複雜度 跳錶的高度 跳錶的高度是log2n。 跳錶的時間複雜度 跳錶中查詢某個資料的時間複雜度是O(logn)。  

資料結構演算法內功修煉——為什麼學習資料結構演算法及如何高效的學習資料結構演算法

什麼是資料結構和演算法 用一句話總結資料結構和演算法,資料結構和演算法是用來儲存資料和處理資料的;其中的儲存指的是通過怎樣的儲存結構來儲存資料,而處理就是通過怎樣的方式或者方法處理資料 為什麼學習資料結構和演算法 寫出更加高效能的程式碼 演算法,是一種解決問題的思路

資料結構演算法美》專欄閱讀筆記5——散列表雜湊函式

這應該是看完最呆(沒有想到的那種呆~)的一個小章節了,給作者鼓掌,講的好好。果然抽象能力才是王道 文章目錄 1、散列表 1.1、小概念 1.2、雜湊函式 1

資料結構演算法設計專題---單鏈表的逆序

資料結構和演算法設計專題之---單鏈表的逆序    https://blog.csdn.net/jiangwei0910410003/article/details/37937721 下面來看一下很經典的“單鏈表逆序”問題。很多公司的面試題庫中都有這道題,有的公司明確

資料結構演算法美-堆堆排序

堆和堆排序 如何理解堆 堆是一種特殊的樹,只要滿足以下兩點,這個樹就是一個堆。 ①完全二叉樹,完全二叉樹要求除了最後一層,其他層的節點個數都是滿的,最後一層的節點都靠左排列。 ②樹中每一個結點的值都必須大於等於(或小於等於)其子樹中每個節點的值。大於等於的情況稱為大頂堆,小於等於的情況稱為小頂堆。

資料結構演算法 佇列廣度優先搜尋(BFS)

 佇列和 BFS 廣度優先搜尋(BFS)的一個常見應用是找出從根結點到目標結點的最短路徑。在本文中,我們提供了一個示例來解釋在 BFS 演算法中是如何逐步應用佇列的。 洞悉 1. 結點的處理順序是什麼? 在第一輪中,我們處理根結點。在第二輪中,我們處理根結點旁

C++面試題資料結構演算法

  C++面試題之資料結構和演算法 目錄 1、String原理及實現 2、連結串列的實現 2.1、順序連結串列 2.2、鏈式表 2.3、雙鏈表 2.4、迴圈連結串列 3、佇列 3.1、順序佇列 3.2、鏈式佇列 4、棧 4.1、順序棧

Java資料結構演算法(一)線性結構單鏈表

Java資料結構和演算法(一)線性結構之單鏈表 prev current next -------------- -------------- -------------- | value | next | ->

回爐篇2—資料結構(1)資料結構演算法

想學新東西,隨手拿來一本資料,一部視訊,但總感覺學不會,吸收效率低。不是我們笨,只是開啟他們的方式不對,合適的學習教材,能讓學習過程變得簡單,形象貼近生活。文底是乾貨直通車,好東西大家記得收藏,都是精挑細選的。文章內容是我自己的筆記,是用最簡語言寫的,可能不利於理解,大家想了解更多,