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

2021-08-21 04:49:10 字數 3831 閱讀 9735

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

這種情況下便可將複雜度減小為o(n/2)。這是一種典型的空間換時間的優化思路。

skiplist則 更進一步,採用了分治演算法和隨機演算法設計。將每個節點所能reach到的最遠的節點,兩個節點之間看成是乙個組,整個skiplist被分成了許多個組,而這些組的形成是隨機的,如下圖:

注意,雖然next指標也可以是node**型別的,但是陣列指標比這個好操作。

而且鍊錶中的元素必須是有序的,這樣才能達到類似於二分法查詢元素的效果。所以在插入資料時不像一般鍊錶一樣,在頭或尾插入,他必須要先找到插入的正確位置。怎麼找到插入的位置呢?

鍊錶只能通過遍歷方式來逐一元素比較,只不過這裡跳躍表的每個節點可能有多個上層節點,必須從上層開始遍歷,因為上層節點稀少,而且能夠指引下層節點。就好比國家行政單位,從上到下有省,市,縣,鎮,村行政單位一樣。要找到乙個村,可以從省開始,逐一鎖定範圍。

插入節點說到底也是個構造跳躍表的過程,所插入的節點也要有多層拷貝才行。如何確定所插入節點要砌多少層呢?

查詢資料,一般給出的是由隨機數,得到偶數的次數來決定。為什麼要這麼做就不深入**了,反正可以到達乙個很好的平衡點,不像紅黑樹,要經過那麼複雜的左旋右旋來保證節點平衡。

跳躍表也有頭節點的概念,初始時自己定義乙個常量,用來生成頭結點有多少層。如果在插入的過程中,新生成的節點層數是最高的,那麼也要讓頭結點指向新增的層,如圖所示:

查詢節點的思想在插入節點的過程中已得到體現,這裡就不再贅述。

完整**如下:

#include #include #include #include #define max_l 16 //最大層數  

//new_node生成乙個node結構體,同時生成包含n個node *元素的陣列

#define new_node(n) ((node*)malloc(sizeof(node)+n*sizeof(node*)))

//定義key和value的型別

typedef int keytype;

typedef int valuetype;

//每個節點的資料結構

typedef struct node

node;

//跳表結構

typedef struct skip_list

skip_list;

node *create_node(int level, valuetype val);

skip_list *create_sl();

/*插入元素的時候元素所占有的層數完全是隨機演算法,返回隨機層數

*/int randomlevel();

bool insert(skip_list *sl, valuetype val);

bool erase(skip_list *sl, keytype key);

valuetype *search(skip_list *sl, keytype key);

/*從最高層開始逐層列印

sl 跳表指標

*/void print_sl(skip_list *sl);

void sl_free(skip_list *sl);

// 建立節點

node *create_node(int level, valuetype val)

//建立跳躍表

skip_list *create_sl()

sl->head = h;

int i;

// 將header的next陣列清空

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

srand(time(0));

return sl;

}//插入元素的時候元素所占有的層數完全是隨機演算法

int randomlevel()

/*step1:查詢到在每層待插入位置,跟新update陣列

step2:需要隨機產生乙個層數

step3:從高層至下插入,與普通鍊錶的插入完全相同。

*/bool insert(skip_list *sl, valuetype val)

if (q && q->value == val)

/******************step2*******************/

//產生乙個隨機層數level

int level = randomlevel();

//如果新生成的層數比跳表的層數大

if (level > sl->level)

sl->level = level;

} //printf("%d\n", sizeof(node)+level*sizeof(node*));

/******************step3*******************/

//新建乙個待插入節點,一層一層插入

q = create_node(level, val);

if (!q)

return false;

//逐層更新節點的指標,和普通鍊錶插入一樣

for (i = level - 1; i >= 0; --i)

return true;

}// 刪除節點

bool erase(skip_list *sl, valuetype val)

update[i] = p;

} //判斷是否為待刪除的key

if (!q || (q&&q->value != val))

return false;

//逐層刪除與普通鍊錶刪除一樣

for (i = sl->level - 1; i >= 0; --i)

} free(q);

q = null;

return true;

}// 查詢

valuetype *search(skip_list *sl, keytype key)

if (q && key == q->value)

return &(q->value);

} return null;

}//從最高層開始逐層列印

void print_sl(skip_list *sl)

printf("\n"); }}

// 釋放跳躍表

void sl_free(skip_list *sl)

free(sl);

}int main()

; skip_list *sl = create_sl();

int i = 0;

for (; i < 9; ++i)

erase(sl, 5);

print_sl(sl);

int *p = search(sl, 5);

if (p)

printf("value %d found!!!\n", *p);

sl_free(sl);

return 0;

}

參看:

redis原始碼學習之跳躍表

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

redis原始碼解析 跳躍表

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

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

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