要理解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次 ...