1. 程式人生 > >跳錶SkipList

跳錶SkipList

1.聊一聊跳錶作者的其人其事

2. 言歸正傳,跳錶簡介

3. 跳錶資料儲存模型

4. 跳錶的程式碼實現分析

5. 論文,程式碼下載及參考資料 

<1>. 聊一聊作者的其人其事

跳錶是由William Pugh發明。他在 Communications of the ACM June 1990, 33(6) 668-676 發表了Skip lists: a probabilistic alternative to balanced trees,在該論文中詳細解釋了跳錶的資料結構和插入刪除操作。

William Pugh同時還是FindBug(沒有使用過,這是一款java的靜態程式碼分析工具,直接對java 的位元組碼進行分析,能夠找出java位元組碼中潛在很多錯誤。)作者之一。現在是University of Maryland, College Park(馬里蘭大學伯克分校,位於馬里蘭州,全美大學排名在五六十名左右的樣子)大學的一名教授。他和他的學生所作的研究深入的影響了java語言中記憶體池實現。

又是一個計算機的天才!

<2>. 言歸正傳,跳錶簡介

這是跳錶的作者,上面介紹的William Pugh給出的解釋:

Skip lists are a data structure that can be used in place of balanced trees. Skip lists use probabilistic balancing rather than strictly enforced balancing and as a result the algorithms for insertion and deletion in skip lists are much simpler and significantly faster than equivalent algorithms for balanced trees.

跳錶是平衡樹的一種替代的資料結構,但是和紅黑樹不相同的是,跳錶對於樹的平衡的實現是基於一種隨機化的演算法的,這樣也就是說跳錶的插入和刪除的工作是比較簡單的。

下面來研究一下跳錶的核心思想:

先從連結串列開始,如果是一個簡單的連結串列,那麼我們知道在連結串列中查詢一個元素I的話,需要將整個連結串列遍歷一次。

 

 如果是說連結串列是排序的,並且節點中還儲存了指向前面第二個節點的指標的話,那麼在查詢一個節點時,僅僅需要遍歷N/2個節點即可。

 

這基本上就是跳錶的核心思想,其實也是一種通過“空間來換取時間”的一個演算法,通過在每個節點中增加了向前的指標,從而提升查詢的效率。

<3>.跳錶的資料儲存模型

 我們定義:

如果一個基點存在k個向前的指標的話,那麼陳該節點是k層的節點。

一個跳錶的層MaxLevel義為跳錶中所有節點中最大的層數。

下面給出一個完整的跳錶的圖示:

 

 那麼我們該如何將該資料結構使用二進位制儲存呢?通過上面的跳錶的很容易設計這樣的資料結構:

定義每個節點型別:

// 這裡僅僅是一個指標typedef struct nodeStructure *node;

typedef struct nodeStructure

{    keyType key;// key值    valueType value;// value值    // 向前指標陣列,根據該節點層數的    // 不同指向不同大小的陣列    node forward[1];};

上面的每個結構體對應著圖中的每個節點,如果一個節點是一層的節點的話(如7,12等節點),那麼對應的forward將指向一個只含一個元素的陣列,以此類推。

 定義跳錶資料型別:

// 定義跳錶資料型別typedef struct listStructure{   int level;  /* Maximum level of the list (1 more than the number of levels in the list) */   struct nodeStructure * header; /* pointer to header */

   } * list; 

跳錶資料型別中包含了維護跳錶的必要資訊,level表明跳錶的層數,header如下所示:

 

定義輔助變數:

定義上圖中的NIL變數:node NIL;

#define MaxNumberOfLevels 16

#define MaxLevel (MaxNumberOfLevels-1) 

定義輔助方法:

// newNodeOfLevel生成一個nodeStructure結構體,同時生成l個node *陣列指標#define newNodeOfLevel(l) (node)malloc(sizeof(struct nodeStructure)+(l)*sizeof(node *))

好的基本的資料結構定義已經完成,接下來來分析對於跳錶的一個操作。 

<4>. 跳錶的程式碼實現分析

4.1 初始化

初始化的過程很簡單,僅僅是生成下圖中紅線區域內的部分,也就是跳錶的基礎結構:

 

list newList(){  list l;  int i;// 申請list型別大小的記憶體  l = (list)malloc(sizeof(struct listStructure));  // 設定跳錶的層level,初始的層為0層(陣列從0開始)  l->level = 0;  // 生成header部分  l->header = newNodeOfLevel(MaxNumberOfLevels);  // 將header的forward陣列清空  for(i=0;i<MaxNumberOfLevels;i++) l->header->forward[i] = NIL;  return(l);

};  

4.2 插入操作

由於跳錶資料結構整體上是有序的,所以在插入時,需要首先查詢到合適的位置,然後就是修改指標(和連結串列中操作類似),然後更新跳錶的level變數。

 

boolean insert(l,key,value) register list l;register keyType key;register valueType value;{  register int k;  // 使用了update陣列  node update[MaxNumberOfLevels];  register node p,q;  p = l->header;  k = l->level;  /*******************1步*********************/  do {// 查詢插入位置while (q = p->forward[k], q->key < key)p = q;// 設定update陣列update[k] = p;} while(--k>=0);// 對於每一層進行遍歷// 這裡已經查詢到了合適的位置,並且update陣列已經// 填充好了元素   if (q->key == key)   {     q->value = value;return(false);};// 隨機生成一個層數   k = randomLevel();   if (k>l->level)   {  // 如果新生成的層數比跳錶的層數大的話    // 增加整個跳錶的層數k = ++l->level;// 在update陣列中將新新增的層指向l->headerupdate[k] = l->header;};/*******************2步*********************/// 生成層數個節點數目  q = newNodeOfLevel(k);  q->key = key;  q->value = value;// 更新兩個指標域  do   {p = update[k];q->forward[k] = p->forward[k];p->forward[k] = q;} while(--k>=0);// 如果程式執行到這裡,程式已經插入了該節點  return(true);

4.3 刪除某個節點

和插入是相同的,首先查詢需要刪除的節點,如果找到了該節點的話,那麼只需要更新指標域,如果跳錶的level需要更新的話,進行更新。

 

boolean delete(l,key) register list l;register keyType key;{  register int k,m;  // 生成一個輔助陣列update  node update[MaxNumberOfLevels];  register node p,q;  p = l->header;  k = m = l->level;  // 這裡和查詢部分類似,最終update中包含的是:  // 指向該節點對應層的前驅節點  do   {while (q = p->forward[k], q->key < key) p = q;update[k] = p;} while(--k>=0);// 如果找到了該節點,才進行刪除的動作  if (q->key == key)   {  // 指標運算for(k=0; k<=m && (p=update[k])->forward[k] == q; k++) // 這裡可能修改l->header->forward陣列的值的   p->forward[k] = q->forward[k];// 釋放實際記憶體free(q);// 如果刪除的是最大層的節點,那麼需要重新維護跳錶的// 層數level   while( l->header->forward[m] == NIL && m > 0 )     m--;l->level = m;return(true);}  else  // 沒有找到該節點,不進行刪除動作   return(false);

4.4 查詢

查詢操作其實已經在插入和刪除過程中包含,比較簡單,可以參考原始碼。 

<5>. 論文,程式碼下載及參考資料

//--------------------------------------------------------------------------------

增加跳錶c#實現程式碼 2011-5-29下午 

上面給出的資料結構的模型是直接按照跳錶的模型得到的,另外還有一種資料結構的模型:

跳錶節點型別,每個跳錶型別中僅僅儲存了左側的節點和下面的節點:

 

我們現在來看對於這種模型的操作程式碼:

1. 初始化完成了如下的操作:

 

2. 插入操作:和上面介紹的插入操作是類似的,首先查詢到插入的位置,生成update陣列,然後隨機生成一個level,然後修改指標。

3. 刪除操作:和上面介紹的刪除操作是類似的,查詢到需要刪除的節點,如果查詢不到,丟擲異常,如果查詢到的需要刪除的節點的話,修改指標,釋放刪除節點的記憶體。

程式碼下載: