跳錶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
{ 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. 刪除操作:和上面介紹的刪除操作是類似的,查詢到需要刪除的節點,如果查詢不到,丟擲異常,如果查詢到的需要刪除的節點的話,修改指標,釋放刪除節點的記憶體。
程式碼下載: