【數據結構與算法之美】跳錶:爲什麼Redis一定要用跳錶來實現有序集合?(Redis跳錶、ConcurrentSkipListMap、紅黑樹、TreeMap)

目錄

一、什麼是跳錶?

二、跳錶的時間複雜度?

三、跳錶的空間複雜度及如何優化?

四、高效的動態插入和刪除?

五、跳錶索引動態更新?

六、課後作業


一、什麼是跳錶?

爲一個值有序的鏈表建立多級索引,比如每2個節點提取一個節點到上一級,我們把抽出來的那一級叫做索引或索引層。如下圖所示,其中down表示down指針,指向下一級節點。以此類推,對於節點數爲n的鏈表,大約可以建立log2n-1級索引。像這種爲鏈表建立多級索引的數據結構就稱爲跳錶。

加來一層索引之後,查找一個結點需要遍歷的結點個數減少了,也就是說查找效率提高了;

Redis用跳錶來實現有序集合;

二、跳錶的時間複雜度?

1.計算跳錶的高度
如果鏈表有n個節點,每2個節點抽取抽出一個節點作爲上一級索引的節點,那第1級索引的節點個數大約是n/2,第2級索引的節點個數大約是n/4,依次類推,第k級索引的節點個數就是n/(2^k)。假設索引有h級別,最高級的索引有2個節點,則有n/(2^h)=2,得出h=log2n-1,包含原始鏈表這一層,整個跳錶的高度就是log2n。
2.計算跳錶的時間複雜度
假設我們在跳錶中查詢某個數據的時候,如果每一層都遍歷m個節點,那在跳錶中查詢一個數據的時間複雜度就是O(m*logn)。那這個m是多少呢?如下圖所示,假設我們要查找的數據是x,在第k級索引中,我們遍歷到y節點之後,發現x大於y,小於後面的節點z,所以我們通過y的down指針,從第k級下降到第k-1級索引。在第k-1級索引中,y和z之間只有3個節點(包含y和z),所以,我們在k-1級索引中最多隻需要遍歷3個節點,以此類推,每一級索引都最多隻需要遍歷3個節點。所以m=3。因此在跳錶中查詢某個數據的時間複雜度就是O(logn)。

三、跳錶的空間複雜度及如何優化?

1.計算索引的節點總數
如果鏈表有n個節點,每2個節點抽取抽出一個節點作爲上一級索引的節點,那每一級索引的節點數分別爲:n/2,n/4,n/8,…,8,4,2,等比數列求和n-1,所以跳錶的空間複雜度爲O(n)。
2.如何優化時間複雜度
如果鏈表有n個節點,每3或5個節點抽取抽出一個節點作爲上一級索引的節點,那每一級索引的節點數分別爲(以3爲例):n/3,n/9,n/27,…,27,9,3,1,等比數列求和n/2,所以跳錶的空間複雜度爲O(n),和每2個節點抽取一次相比,時間複雜度要低不少呢。

四、高效的動態插入和刪除?

跳錶本質上就是鏈表,所以僅插作,插入和刪除操時間複雜度就爲O(1),但在實際情況中,要插入或刪除某個節點,需要先查找到指定位置,而這個查找操作比較費時,但在跳錶中這個查找操作的時間複雜度是O(logn),所以,跳錶的插入和刪除操作的是時間複雜度也是O(logn)。

五、跳錶索引動態更新?

當往跳錶中插入數據的時候,可以選擇同時將這個數據插入到部分索引層中,那麼如何選擇這個索引層呢?可以通過隨機函數來決定將這個節點插入到哪幾級索引中,比如隨機函數生成了值K,那就可以把這個節點添加到第1級到第K級索引中。

六、課後作業

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

解答:插入、刪除、查找以及迭代輸出有序序列這幾個操作,紅黑樹也可以完成,時間複雜度跟跳錶是一樣的。但是,按照區間來查找數據這個操作,紅黑樹的效率沒有跳錶高。對於按照區間查找數據這個操作,跳錶可以做到 O(logn) 的時間複雜度定位區間的起點,然後在原始鏈表中順序往後遍歷就可以了。這樣做非常高效。

Redis 之所以用跳錶來實現有序集合,還有其他原因,比如,跳錶更容易代碼實現。雖然跳錶的實現也不簡單,但比起紅黑樹來說還是好懂、好寫多了,而簡單就意味着可讀性好,不容易出錯。還有,跳錶更加靈活,它可以通過改變索引構建策略,有效平衡執行效率和內存消耗。

1. 跳錶如果每三個或者五個結點提取一個結點作爲上級索引,對應的在跳錶中查詢數據的時間複雜度是多少呢?

解答:如果每三個或者五個節點提取一個節點作爲上級索引,那麼對應的查詢數據時間複雜度,應該也還是 O(logn)。
假設每 5 個節點提取,那麼最高一層有 5 個節點,而跳錶高度爲 log5n,每層最多需要查找 5 個節點,即 O(mlogn) 中的 m = 5,最終,時間複雜度爲 O(logn)。
空間複雜度也還是 O(logn),雖然省去了一部分索引節點,但是似乎意義不大。
參考跳錶的 Java 實現ConcurrentSkipListMap。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章