redis原始碼學習之跳躍表

2021-07-26 09:28:57 字數 2955 閱讀 7891

跳躍表對於我來說是乙個比較陌生的資料結構,因此花了一上午的時間先看了一蛤mit的公開課。網易雲課堂——mit跳躍表

什麼是跳躍表,有乙個很簡單的例子,有些地方的火車站跟高鐵站是同乙個站,有的地方只有火車站;假設現在的線路是a-b-c-d-e。其中a和c剛剛說的高鐵和火車站在一塊,其他的只有火車站,考慮這個例子,其實乙個簡單的跳躍表已經初見雛形,比如:

a---c

|   |

a-b-c-d-e

假設我們要從a到e,我們不必坐普通火車經停b、c、d到e,如果趕時間,我們可以直接做高鐵a-c再在c做普通火車經停d到e。

再如果,繼續剛才的例子,如果a、d既有高鐵也有火車還有飛機呢?剛才的圖變成這樣:

a————d

a——-c—-d

a—b—c—-d—e

此時我們將有更快的方法,從a-d乘坐飛機,再從d-e乘坐火車。

每一層都是簡單的單鏈表,最底層包含所有的元素,越往上,將會保留部分元素,每一層之間之間相同元素有一條「通路」,高層的鍊錶可以理解為快車道,因為經停的站少,而最底層是慢車道,包含了所有元素。

跳躍表設計的初衷有點類似二叉查詢樹,當我們要查詢某個元素b時,比如從a出發,首先在飛機航線上找,發現b < d;接著向下,到了高鐵站,看到b < c;接著向下到了普通車站,往右找到了b。表中元素較多時,跳躍表的查詢效率還是比較可觀的o(

logn

) 我們應該如何確定某個元素到底要不要修高鐵站或者飛機場?實際問題可能比較複雜,但在數學上,用拋硬幣的方式決定,或者以某個特定的概率p1

來決定,p1

表示元素a出現在第i層,那麼它將有p1

的概率出現在第i+1層,很顯然這是乙個幾何分布,幾何分布的數學期望是1p

,根據此可以得出乙個元素出現在11

−p1 層(直到不出現為止)。

假設要在這個表中插入5.5,現在我們已經找到了它的位置,就在5和6之間。那麼像普通鍊錶插入那樣,然後拋硬幣,如果正,則往上再構造一層,如此往復,直到拋到反面。

在redis中,跳躍表的應用是有序集,key是redis的obj物件,score分值是乙個double型別,比較是先比較score的大小,如果score相同,再使用下面函式比較鍵值。

/* 以redis_compare_binary的方式比較兩字串物件 */

int comparestringobjects(robj *a, robj *b);

**見src/t_zset.credis不是用拋硬幣,或者某個固定概率的方式來插入元素,它的方式更加簡單暴力:用隨機數的方式,直接生成乙個1-zskiplist_maxlevel的值,其中zskiplist_maxlevel為32可以在巨集定義中找到。根據隨機演算法所使用的冪次定律,越大的值生成的機率越小。

int zslrandomlevel(void)  level;

} zskiplistnode;

因此,redis做插入的時候,將會從32開始往下遍歷,然後依次比較,如果插入的分值比key大,則繼續往右,否則將往下走,這點規則符合跳躍表,只是實現上不一樣而已。

zskiplistnode *zslinsert(zskiplist *zsl, double score, robj *obj) 

// 記錄將要和新節點相連線的節點

update[i] = x;

}/* we assume the key is not already inside, since we allow duplicated

* scores, and the re-insertion of score and redis object should never

* if the element is already inside or not.

** zslinsert() 的呼叫者會確保同分值且同成員的元素不會出現,

* 所以這裡不需要進一步進行檢查,可以直接建立新元素。

*/// 獲取乙個隨機值作為新節點的層數

// t = o(n)

level = zslrandomlevel();

// 如果新節點的層數比表中其他節點的層數都要大

// 那麼初始化表頭節點中未使用的層,並將它們記錄到 update 陣列中

// 將來也指向新節點

if (level > zsl->level)

// 更新表中節點最大層數

zsl->level = level;

}// 建立新節點

x = zslcreatenode(level,score,obj);

// 將前面記錄的指標指向新節點,並做相應的設定

// t = o(1)

for (i = 0; i < level; i++)

/* increment span for untouched levels */

// 未接觸的節點的 span 值也需要增一,這些節點直接從表頭指向新節點

// t = o(1)

for (i = level; i < zsl->level; i++)

// 設定新節點的後退指標

x->backward = (update[0] == zsl->header) ? null : update[0];

if (x->level[0].forward)

x->level[0].forward->backward = x;

else

zsl->tail = x;

// 跳躍表的節點計數增一

zsl->length++;

return x;

}

redis原始碼解析 跳躍表

定義 跳躍表是一種有序資料結構,它通過在每個節點中維護多個指向其他節點的指標,從而達到快速訪問節點的目的。跳躍表支援平均o logn 最壞o n 複雜度的節點查詢,大部分情況下,跳躍表的效率可以和平衡樹相媲美,並且因為跳躍表的實現比平衡樹要來得簡單,所以有不少程式都使用跳躍表來替代平衡樹。從圖中可以...

redis 原始碼學習筆記 跳躍表簡單編碼實現

要理解skiplist,得先從linkedlist說起。linkedlist 增刪查改的時間複雜度都是o n 它最大的問題就是通過乙個節點只能reach到下乙個節點 double linkedlist 是一種改進方案 那麼改進的思路就是通過乙個節點reach到多個節點,例如下圖 這種情況下便可將複雜...

Redis(三)跳躍表介紹及原始碼

查詢鍊錶的時間複雜度o n 即使該鍊錶的是有序的,但若我們在鍊錶上在加一層鏈,且每次跳過乙個節點 即進行一次二分 如下圖所示 若在l2鏈的基礎上增加一層鏈,在每次跳過l2上的乙個節點 即在l2鏈上進一步二分 那麼我便可以進一步增加搜尋速度,如下圖所示 此時查詢8只需在l3上搜尋2次,查詢7需要3次 ...