LevelDB中的Skip List(跳躍表)轉載:http://blog.nosqlfan.com/html/3041.html
本文是關於Skip
List資料結構的,Skip List是在有序List(鏈表)資料結構的基礎上的擴充,解決了有序鏈表結構尋找特定值困難的問題,使用Skip List,可以使得在一個有序鏈表裡尋找特定值的時間複雜度為O(logn),在本文中我們看到,Skip List被用在leveldb中,實際上它還被使用在Redis的sorted
sets資料結構中。
這段時間在關注leveldb。leveldb中有一個核心的資料結構skiplist,skip
list和單鏈表類似,只不過有些節點有前向指標以便加快遍曆,有k個前向指標的節點叫做level k node。
本部落客要介紹skiplist的演算法原理,包括skiplist增刪改查,下一篇部落格將介紹skiplist的複雜度分析。(部落格內容主要是翻譯Skip
Lists: A probabilistic Alternative to Balanced Trees)
Skip list(跳躍表)是一種可以代替平衡樹的資料結構。Skip lists應用機率保證平衡,平衡樹採用嚴格的旋轉(比如平衡二叉樹有左旋右旋)來保證平衡,因此Skip list比較容易實現,而且相比平衡樹有著較高的運行效率。
從機率上保持資料結構的平衡比顯示的保持資料結構平衡要簡單的多。對於大多數應用,用skip list要比用樹更為自然,演算法也會相對簡單。由於skip list比較簡單,實現起來會比較容易,雖然和平衡樹有著相同的時間複雜度(O(logn)),但是skip list的常數項會相對小很多。skip list在空間上也比較節省。一個節點平均只需要1.333個指標(甚至更少),並且不需要儲存報紙平衡的變數。
Skip Lists
鏈表中的值,非遞減順序排列。
- 圖a:為了尋找單鏈表中的某個值,最壞情況下需要將鏈表全部遍曆一遍,需要遍曆n個節點。
- 圖b:每2個節點儲存了它後面第2個節點,知識最多需要遍曆n/2 + 1個節點。
- 圖c:圖b基礎上每4個節點儲存前面第4個節點內容,這時最多遍曆n/4 + 2個節點。(n/4 + 4/2)
- 圖d:如果每2^i個節點都指向前面2^i個節點,尋找一個節點的複雜度變成logn(類似於二分尋找)。雖然這種結構尋找很快但是插入刪除卻很複雜。
有著k個前向指標(farword pointers)的節點叫做level k node。如果每2^i的節點指向前面2^i個後繼節點,那麼節點的分布情況為:50% 在第一層,25%在第二層,12.5%在第3層。如果所有節點的層數是隨機挑選的。節點第i個前向指標指向後面第2^(i-1)個節點。插入和刪除只需要局部修改少數指標,節點的層數(level)在插入時隨機選取,並且以後不需要修改。雖然有一些指標的排列會導致很壞的已耗用時間,但是這些情況很少出現。
初始化
首先申請一個NIL節點,此節點的Key賦一個最大值作為哨兵節點。
鏈表的level置為1,頭結點所有的forward pointer指向NIL節點。
尋找
為了找到要尋找的值,我們逐次遍曆forward pointer。
當指標在level 1層不能繼續前進時,我們肯定在需要節點的前一個節點處(如果鏈表中存在要尋找的節點)
Search(list, searchKey) x := list->header //loop invariant: x->key < searchKey for i := list->level downto 1 do while x->forward[i]->key < searchKey do x := x->forward[i] //x->key < sarchKey <= x->forward[1]->key x := x->forward[1] if x->key = searchKey then rturn x->value else return failure
隨機播放層數
之前討論時層數的選擇是按照1/2(p=1/2)的機率選擇的,p可以取[0, 1)間的任意值,演算法如下所示。
randomLevel() |v| : =1 //random()that returns a random value in [0..1) while random() < p and |v| < MaxLevel do |v| := |v| + 1 return |v|
Insert(list, searchKey, newValue) local update[1..MaxLevel] x : =list->header for i := list->level donwto 1 do while x->forward[i]->key < searchKey do x := x->forward[i] //x->key < searchKey <= x->forward[i]->key update[i] := x x := x->forward[1] if x->key = searchKey then x->value := newValue else |v| := randomLevel() if |v| > list->level then for i := list->level + 1 to |v| do update[i] := list->header list->level := |v| x := makeNode(|v|, searchKey, value) for i := 1 to level do x->forward[i] := update[i]->forward[i] update[i]->forward[i] := x
Delete(list searchKey) local update[1..MaxLevel] x := list->header for i := list->level downto 1 do while x->forward[i]->key < searchKey do x := x->forward[i] update[i] := x x := x->forward[1] if x->key = searchKey then for i := 1 to list->level do if update[i]->forward[i] != x then break update[i]->forward[i] := x->forward[i] free(x) while list->leve > 1 and list->header->forward[list->level] = NULL do list->level := list->level - 1
結論
從理論的角度看,skiplist是完全沒有必要的。Skip lists能做的事情平衡樹也同樣能做,並且在最壞情況下的時間複雜度比Skip lists要好。但是實現平衡樹卻是一項複雜的工作,除了在資料結構課程上實現平衡樹外,實際應用中很少會實現它。
作為一種簡單的資料結構,在大多數應用中Skip lists能夠代替平衡樹。Skip lists演算法非常容易實現、擴充和修改。Skip lists和進行過最佳化的平衡樹有著同樣高的效能,Skip lists的效能遠遠超過未經最佳化的平衡二叉樹。
來源:blog.xiaoheshang.info
看到大家在微博上對Skip List討論非常激烈,再分享一個講Skip List的圖文並茂的PPT:
skip
list
View more presentations from iammutex