跳表是一種可以替代平衡樹的資料結構。跳表追求的是概率性平衡,而不是嚴格平衡。因此,跟平衡二叉樹相比,跳表的插入和刪除操作要簡單得多,執行也更快。
跳表是一種概率性可行的平衡二叉樹替代資料結構。跳表通過乙個隨機數生成器實現平衡。雖然跳表最壞情況下(worst-case
)效能也很差,但是沒有任何輸入序列必然會導致最壞情況發生(這點類似劃分元素(pivot point
)隨機選定的快排)。跳表極度不平衡發生的概率非常低(乙個包含250
個元素的字典,一次查詢需要花3
倍期望時間的概率小於百萬分之一)。跳表平衡概率跟隨機插入的二叉樹差不多,好處是插入順序不要求隨機。
實現概率性平衡比嚴格控制平衡要簡單得多。對很多應用來說,跳表用起來比平衡樹更自然,而且演算法更簡單。跳表演算法簡單性意味著更容易實現,而且與平衡樹和自適應樹相比有常數倍數的效能提公升。跳表在空間上也比較高效。平均每個元素只需要額外耗費個2指標(甚至可以配置得更低),並不需要在每個節點上都存與平衡和優先順序相關的資料。
搜尋乙個鍊錶時,我們需要遍歷每個節點(如圖 1a)。如果列表是有序的,偶數節點另存乙個指向下乙個偶數節點的指標(如圖 1b),我們只需要檢查最多(n/2)+1
個節點(n
是鍊錶規模)。如果序號為4
的倍數的節點都有乙個往前跳4
步的節點,那麼最多隻需要檢查(n/4)+2
次。如果,序號為2^i
的節點有乙個向前跳2^i
步的指標,那麼則需要檢查log2 n
次了!這種資料結構可以用來做快速搜尋,但是插入和刪除並沒有可行性。
有k
個前進指標的節點成為k
層節點。如果第2^i
個節點有乙個向前跳2^i
步的指標,那麼每層節點數滿足以下關係:第1
層有50%
的節點;第2
層有25%
的節點;第3
層有12.5%
的節點;以此類推。假設每層的比例還是一樣,但是節點隨機選擇,會怎樣呢(圖 1e)?節點第i
個前進指標不嚴格跳2^i
步,而是可以跳任意步。由於不需要維持特殊條件,插入節點層數隨機生成,插入和刪除只需要做區域性修改。極端情況下,有些層次分布會導致極差的效能,不過接下來我們會看到這種情況非常罕見。這種資料結構在鍊錶的基礎上加上額外指標以跳過一些中間節點,因此命名為跳表。
這小節介紹用於搜尋、插入、刪除的演算法。搜尋操作返回與給定鍵(key
)關聯的值(value
),鍵不存在時則失敗。插入操作將給定鍵關聯到新的值,如果鍵不存在則插入新的節點。刪除操作刪除給定鍵。另外,類似最小鍵和下一鍵這類操作實現起來也非常簡單。
每個元素由乙個節點表示,層次由節點在插入時隨機選定,與已有元素無關。層次為i
的節點擁有i
個前進指標,下標分別是1
至i
。節點不需要儲存層數。選定乙個合適的常量maxlevel
,層數在這個範圍內。跳表的層數時當前所有節點層數的最大值,或者當跳表為空是,層數為1
。用乙個頭向量儲存從層次1
到maxlevel
的向前指標。指標高於當前跳表層數的部分直接指向nil
。
約定nil
元素,其鍵比所有合法建都大(上限)。跳表的任意層都以nil
結尾。新的跳表初始化成層數只有1
,並且所有表頭所有前進指標都指向nil
。
查詢某個元素時,需要逐層遍歷所有鍵不超過給定鍵的節點。如果當前層前進節點已經不符合條件了,往下一層開始遍歷。當遍歷進行到第1
層時,下乙個節點就是目標節點(如存在)。
search(list, searchkey)
x := list->header
for i := list->level downto 1 do
while x->forward[i]->key < searchkey do
x = x->forward[i]
x := x->forward[1]
if x->key = searchkey
then
return x->value
else
return failure
插入或者刪除節點,只需先執行搜尋操作(圖 3),然後視情況重新拼接。偽**如下所示:
圖3展示了搜尋過程。注意到,搜尋的過程中維護了乙個名為update
的向量,在每次降層搜尋時更新。搜尋完成後,update
剛好記錄了各層在操作位置(圖中環)左邊最近的節點:
元素節點
update[1]
12update[2]
9update[3]
6update[4]
6如果插入時生成了乙個比當前最大層更大的層數,則需要更新跳表層數並且初始化update
向量對應部分。
接下來,看看刪除操作的偽**:
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[i]
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->level > 1 and list->header->forward[list->level] = nil do
list->level := list->level - 1
在每次刪除時,需要檢查被刪除節點是否是最大層節點。如果是,需要對跳表層數做對應調整。
接下來,需啊確定乙個隨機數生成函式,其概率分布使得第i
層中有50%
的節點同時資料第i+1
層。先拋開具體數值,我們在討論乙個分數p
,對於有i
層指標的節點中p
部分,同時擁有i+1
層指標。以下便是乙個非常理想的隨機數生成函式,隨機層數生成與跳表元素及規模無關:
randomlevel()
lvl := 1
while random() < p and lvl < maxlevel do
lvl := lvl + 1
return lvl
虛函式的一種替代方案
虛函式的目的是實現物件的動態繫結,但是有些情況下,可能換一種替代方案,可能會使類的設計更加穩定,易用,下面列出乙個我認為很棒的虛函式的替代方案。使用non virtual inte ce nvi 手法,它以public non virtual 成員函式包裹較低訪問性的virtual函式 class ...
一種簡單的色彩平衡演算法的OPENCV實現
很久之前看過一篇關於色彩平衡文章,在該文章中介紹了一種非常簡單高效的 色彩平衡演算法。下圖是演算法的演示效果 左邊為處理後的影象,右邊為處理前的影象 從圖中可以看出演算法有效的改善的影象的色彩,使得色彩更加的真實。下面是實現 include stdafx.h include cv.h include...
Solr巢狀子文件的弊端以及一種替代方式
背景 在考察了多種工具後,我們決定使用solr來作為多標籤使用者管理體系的查詢方案。原計畫 call客,跟進等等記錄上報到kafka,然後通過flume morphline錄入到solr中。每乙個使用者是乙個獨立的父文件,然後每新增一條來電 call客 跟進記錄,則會在使用者的父文件下增加乙個巢狀子...