資料結構和演算法之——跳錶
之前我們知道,二分查詢依賴陣列的隨機訪問,所以只能用陣列來實現。如果資料儲存在連結串列中,就真的沒法用二分查找了嗎?而實際上,我們只需要對連結串列稍加改造,就可以實現類似“二分”的查詢演算法,這種改造之後的資料結構叫作跳錶(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」!
相關推薦
資料結構和演算法之——跳錶
之前我們知道,二分查詢依賴陣列的隨機訪問,所以只能用陣列來實現。如果資料儲存在連結串列中,就真的沒法用二分查找了嗎?而實際上,我們只需要對連結串列稍加改造,就可以實現類似“二分”的查詢演算法,這種改造之後的資料結構叫作跳錶(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)之資料結構和演算法
想學新東西,隨手拿來一本資料,一部視訊,但總感覺學不會,吸收效率低。不是我們笨,只是開啟他們的方式不對,合適的學習教材,能讓學習過程變得簡單,形象貼近生活。文底是乾貨直通車,好東西大家記得收藏,都是精挑細選的。文章內容是我自己的筆記,是用最簡語言寫的,可能不利於理解,大家想了解更多,