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

2021-09-08 07:30:03 字數 3527 閱讀 2154

查詢鍊錶的時間複雜度o(n),即使該鍊錶的是有序的,但若我們在鍊錶上在加一層鏈,且每次跳過乙個節點(即進行一次二分),如下圖所示:

若在l2鏈的基礎上增加一層鏈,在每次跳過l2上的乙個節點(即在l2鏈上進一步二分),那麼我便可以進一步增加搜尋速度,如下圖所示:

此時查詢8只需在l3上搜尋2次,查詢7需要3次(從l3搜尋到節點4,轉至l2搜尋至節點6,轉至l3搜尋至節點7)。

最後再在l3基礎上增加一層鏈,即再進行一次二分,如下圖所示:

這便是理想跳躍表,在鍊錶的基礎上不斷進行二分,共logn層(n為節點個數),查詢的時間為2logn,因為每一層至多進行2次比較。由此可知為什麼說跳躍表的效能可以和平衡樹相媲美。

可是這樣一來為了維護乙個理想跳躍表,刪除和新增時都要調整各層鍊錶,而這十分耗時。因此大多跳躍表都利用概率論實現近似理想跳躍表。

由第一節可知每一層鏈都是在前一次鏈的基礎上進行的二分,每兩個節點鏈入乙個,即鏈入1/2的節點,而這可以通過概率來近似,即每個鏈入li層的節點有1/2的概率鏈入l(i + 1)層。從而可知乙個節點鏈入l1的概率為1,鏈入l2的概率為1/2,鏈入l3的概率為1/4,一次類推1/8,1/16,1/32....。

在程式中如何實現呢?可以採用隨機數的方式,隨機產生[ 1 ~ 2^maxlevel ]的數字,那麼l1概率對應的範圍為1~2^maxlevel,即概率為1;l2概率對應的範圍為2^(maxlevel  - 1) ~ 2^maxlevel,即概率為1/2;l3層對應的範圍為2(maxlevel  - 2) ~ 2^(maxlevel  - 1),即概率為1/4,依次類推,如下圖所示。當資料量較少時不一定十分近似於理想跳躍表,但當資料量較大時根據概率論可知會近似於理想跳躍表。

redis中跳躍表的結構如下所示:

// 跳躍表節點

typedef struct zskiplistnode level;

}zskiplistnode;

typedef struct zskiplist zskiplist;

當建立乙個跳躍表節點時,程式都要根據冪次定律(越大的數出現的概率越小)隨機生成乙個介於1~32之間的值作為level陣列的大小。

【注1】:以上這句是書中給出的,其實就是根據第二節中的方式取出乙個隨機數,從而決定level的大小,也就是要鏈入前幾層鍊錶中。比如,若產生的隨機數在2^(maxlevel  - 1)~2^maxlevel 之間則level大小為2,需鏈入l1,l2;若產生的隨機數在2^(maxlevel  - 2) ~ 2^(maxlevel  - 1)之間則level大小為3,需要鏈入l1,l2,l3。

// 建立跳躍表節點

zskiplistnode *zslcreatenode(int level, double score, robj *obj)

// 建立跳躍表

zskiplist *zslcreate(void)

zsl->header->backward = null;

zsl->tail = null;

return zsl;

}// 釋放跳躍表節點

void zslfreenode(zskiplistnode *node)

// 釋放跳躍表

void zslfree(zskiplist *zsl)

zfree(zsl);

}

int zslrandomlevel(void) 

// update[i]記錄了鏈入第i層的哪個節點之後

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. */

level = zslrandomlevel(); // 根據概率計算應該鏈入跳躍表的哪幾層

if (level > zsl->level)

zsl->level = level;

}x = zslcreatenode(level,score,obj); // x指向新增節點

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

// 對於更高層(為鏈入的層)要更新部分節點的跨度

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;

}

【問】:為什麼不先計算鏈入哪些層,再計算再這些層中的鏈入位置?

【答】:因為更高層也需要計算跨度

在跳躍表中刪除某節點

/* internal function used by zsldelete, zsldeletebyscore and zsldeletebyrank */

// 刪除x指向的節點

void zsldeletenode(zskiplist *zsl, zskiplistnode *x, zskiplistnode **update) else

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

while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == null)

zsl->level--;

zsl->length--;

}int zsldelete(zskiplist *zsl, double score, robj *obj)

x = x->level[0].forward; // 此時x指向要刪除的節點(原先指向前乙個節點),0層的跨度皆為1

if (x && score == x->score && equalstringobjects(x->obj,obj))

return 0; /* not found */

}

redis原始碼解析 跳躍表

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

redis原始碼學習之跳躍表

跳躍表對於我來說是乙個比較陌生的資料結構,因此花了一上午的時間先看了一蛤mit的公開課。網易雲課堂 mit跳躍表 什麼是跳躍表,有乙個很簡單的例子,有些地方的火車站跟高鐵站是同乙個站,有的地方只有火車站 假設現在的線路是a b c d e。其中a和c剛剛說的高鐵和火車站在一塊,其他的只有火車站,考慮...

redis zskiplist 跳躍表原始碼學習

typedef struct zskiplistnode level index越大表示層級越高 zskiplistnode typedef struct zskiplist zskiplist typedef struct zset zset 命令 zadd key score value zsk...