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)
定義輔助方法:
#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->header
update[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>. 論文,代碼下載及參考資料
SkipList論文
/Files/xuqiang/skipLists.rar
//——————————————————————————–
增加跳錶c#實現代碼 2011-5-29下午
上面給出的數據結構的模型是直接按照跳錶的模型得到的,另外還有一種數據結構的模型:
跳錶節點類型,每個跳錶類型中僅僅存儲了左側的節點和下面的節點:
我們現在來看對於這種模型的操作代碼:
初始化完成了如下的操作:
- 插入操作:和上面介紹的插入操作是類似的,首先查找到插入的位置,生成update數組,然後隨機生成一個level,然後修改指針。
- 刪除操作:和上面介紹的刪除操作是類似的,查找到需要刪除的節點,如果查找不到,拋出異常,如果查找到的需要刪除的節點的話,修改指針,釋放刪除節點的內存。