最初知道跳錶(Skip List)是在看redis原理的時候,redis中的有序集合使用了跳錶做爲數據結構。接着就查了一些資料,來學習一下跳錶。後面會使用java代碼來實現跳錶。java
跳錶由William Pugh發明。他在論文《Skip lists: a probabilistic alternative to balanced trees》中詳細介紹了跳錶的數據結構和插入刪除等操做。論文是這麼介紹跳錶的:node
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.redis
也就是說,跳錶能夠用來替代紅黑樹,使用機率均衡技術,插入、刪除操做更簡單、更快。算法
做者在文章中由鏈表的查找一步步引入到跳錶的介紹。首先來看論文裏的一張圖:數組
上圖a,已排好序的鏈表,查找一個結點最多須要比較N
個結點。數據結構
上圖b,每隔2個結點增長一個指針,指向該結點間距爲2的後續結點,那麼查找一個結點最多須要比較ceil(N/2)+1
個結點。dom
上圖c,每隔4個結點增長一個指針,指向該結點間距爲4的後續結點,那麼查找一個結點最多須要比較ceil(N/4)+1
個結點。函數
若是每第2^i
個結點都有一個指向間距爲2^i的後續結點的指針,這樣不斷增長指針,比較次數會降爲log(N)
。這樣的話,搜索會很快,但插入和刪除會很困難。學習
一個擁有k個指針的結點稱爲一個k層結點(level k node)。按照上面的邏輯,50%的結點爲1層,25%的結點爲2層,12.5%的結點爲3層...若是每一個結點的層數隨機選取,但仍服從這樣的分佈呢(上圖e,對比上圖d)?ui
使一個k層結點的第i個指針指向第i層的下一個結點,而不是它後面的第2^(i-1)個結點,那麼結點的插入和刪除只須要原地修改操做;一個結點的層數,是在它被插入的時候隨機選取的,而且永不改變。由於這樣的數據結構是基於鏈表的,而且額外的指針會跳過中間結點,因此做者稱之爲跳錶(Skip Lists)。
原始論文使用僞代碼的形式來描述算法。本文以java語言來描述算法。
首先定義一下須要用的數據結構。表中的元素使用結點來表示,結點的層數在它被插入時隨機計算決定(與表中已有結點數目無關)。一個i層的結點有i個前向指針(java中使用結點對象數組forward來表示),索引爲從1到i。用MaxLevel來記錄跳錶的最大層數。跳錶的層數爲當前全部結點中的最大層數(若是list爲空,則層數爲1)。列表頭header擁有從1到MaxLevel的前向指針:
public class SkipList<T> { // 最高層數 private final int MAX_LEVEL; // 當前層數 private int listLevel; // 表頭 private SkipListNode<T> listHead; // 表尾 private SkipListNode<T> NIL; // 生成randomLevel用到的機率值 private final double P; // 論文裏給出的最佳機率值 private static final double OPTIMAL_P = 0.25; public SkipList() { // 0.25, 15 this(OPTIMAL_P, (int)Math.ceil(Math.log(Integer.MAX_VALUE) / Math.log(1 / OPTIMAL_P)) - 1); } public SkipList(double probability, int maxLevel) { P = probability; MAX_LEVEL = maxLevel; listLevel = 1; listHead = new SkipListNode<T>(Integer.MIN_VALUE, null, maxLevel); NIL = new SkipListNode<T>(Integer.MAX_VALUE, null, maxLevel); for (int i = listHead.forward.length - 1; i >= 0; i--) { listHead.forward[i] = NIL; } } // 內部類 class SkipListNode<T> { int key; T value; SkipListNode[] forward; public SkipListNode(int key, T value, int level) { this.key = key; this.value = value; this.forward = new SkipListNode[level]; } } }
按key搜索,找到返回該key對應的value,未找到則返回null。
經過遍歷forward數組來需找特定的searchKey。假設skip list的key按照從小到大的順序排列,那麼從跳錶的當前最高層listLevel開始尋找searchKey。在某一層找到一個非小於searchKey的結點後,跳到下一層繼續找,直到最底層爲止。那麼根據最後搜索中止位置的下一個結點,就能夠判斷searchKey在不在跳錶中。
下圖爲在跳錶中找8的過程:
插入的刪除的方法類似,都是經過查找與鏈接(search and splice),以下圖:
維護一個update數組,在搜索結束以後,update[i]保存的是待插入/刪除結點在第i層的左側結點。
若key不存在,則插入該key與對應的value;若key存在,則更新value。
若是待插入的結點的層數高於跳錶的當前層數listLevel,則更新listLevel。
選擇待插入結點的層數randomLevel:
randomLevel只依賴於跳錶的最高層數和機率值p。算法在後面的代碼中。
另外一種實現方法爲,若是生成的randomLevel大於當前跳錶的層數listLevel,那麼將randomLevel設置爲listLevel+1,這樣方便之後的查找,在工程上是能夠接受的,但同時也破壞了算法的隨機性。
刪除特定的key與對應的value。
若是待刪除的結點爲跳錶中層數最高的結點,那麼刪除以後,要更新listLevel。
參考了網上的一些代碼,用java寫了一版,包含main函數,可運行。
public class SkipList<T> { // 最高層數 private final int MAX_LEVEL; // 當前層數 private int listLevel; // 表頭 private SkipListNode<T> listHead; // 表尾 private SkipListNode<T> NIL; // 生成randomLevel用到的機率值 private final double P; // 論文裏給出的最佳機率值 private static final double OPTIMAL_P = 0.25; public SkipList() { // 0.25, 15 this(OPTIMAL_P, (int)Math.ceil(Math.log(Integer.MAX_VALUE) / Math.log(1 / OPTIMAL_P)) - 1); } public SkipList(double probability, int maxLevel) { P = probability; MAX_LEVEL = maxLevel; listLevel = 1; listHead = new SkipListNode<T>(Integer.MIN_VALUE, null, maxLevel); NIL = new SkipListNode<T>(Integer.MAX_VALUE, null, maxLevel); for (int i = listHead.forward.length - 1; i >= 0; i--) { listHead.forward[i] = NIL; } } // 內部類 class SkipListNode<T> { int key; T value; SkipListNode[] forward; public SkipListNode(int key, T value, int level) { this.key = key; this.value = value; this.forward = new SkipListNode[level]; } } public T search(int searchKey) { SkipListNode<T> curNode = listHead; for (int i = listLevel; i > 0; i--) { while (curNode.forward[i].key < searchKey) { curNode = curNode.forward[i]; } } if (curNode.key == searchKey) { return curNode.value; } else { return null; } } public void insert(int searchKey, T newValue) { SkipListNode<T>[] update = new SkipListNode[MAX_LEVEL]; SkipListNode<T> curNode = listHead; for (int i = listLevel - 1; i >= 0; i--) { while (curNode.forward[i].key < searchKey) { curNode = curNode.forward[i]; } // curNode.key < searchKey <= curNode.forward[i].key update[i] = curNode; } curNode = curNode.forward[0]; if (curNode.key == searchKey) { curNode.value = newValue; } else { int lvl = randomLevel(); if (listLevel < lvl) { for (int i = listLevel; i < lvl; i++) { update[i] = listHead; } listLevel = lvl; } SkipListNode<T> newNode = new SkipListNode<T>(searchKey, newValue, lvl); for (int i = 0; i < lvl; i++) { newNode.forward[i] = update[i].forward[i]; update[i].forward[i] = newNode; } } } public void delete(int searchKey) { SkipListNode<T>[] update = new SkipListNode[MAX_LEVEL]; SkipListNode<T> curNode = listHead; for (int i = listLevel - 1; i >= 0; i--) { while (curNode.forward[i].key < searchKey) { curNode = curNode.forward[i]; } // curNode.key < searchKey <= curNode.forward[i].key update[i] = curNode; } curNode = curNode.forward[0]; if (curNode.key == searchKey) { for (int i = 0; i < listLevel; i++) { if (update[i].forward[i] != curNode) { break; } update[i].forward[i] = curNode.forward[i]; } while (listLevel > 0 && listHead.forward[listLevel - 1] == NIL) { listLevel--; } } } private int randomLevel() { int lvl = 1; while (lvl < MAX_LEVEL && Math.random() < P) { lvl++; } return lvl; } public void print() { for (int i = listLevel - 1; i >= 0; i--) { SkipListNode<T> curNode = listHead.forward[i]; while (curNode != NIL) { System.out.print(curNode.key + "->"); curNode = curNode.forward[i]; } System.out.println("NIL"); } } public static void main(String[] args) { SkipList<Integer> sl = new SkipList<Integer>(); sl.insert(20, 20); sl.insert(5, 5); sl.insert(10, 10); sl.insert(1, 1); sl.insert(100, 100); sl.insert(80, 80); sl.insert(60, 60); sl.insert(30, 30); sl.print(); System.out.println("---"); sl.delete(20); sl.delete(100); sl.print(); } }