跳躍表
最近在看redis方面的書籍,碰到了跳躍表這個資料結構。常規的單向連結串列在進行增刪改查時,只能從頭結點開始遍歷,時間複雜度O(N),而跳躍表採用了二分法的思想,平均時間複雜度可以做到O(logN),最壞時間複雜度O(N)。另外,跳躍表是一種基於概率的資料結構(見下文新增元素操作)。
基本結構
下圖是跳躍表理想情況下的結構示意圖。從圖中可以看出,
- 每個節點包含兩個指標:指向水平方向的下一個節點和指向豎直方向的下一個節點(實際結構並非如此,後面會說)。
- 跳躍表中包含多條連結串列。圖中包含4條連結串列,也就是有4層。每層的頭結點head只存放指向下個節點的指標,不存放元素。可以看出,從下向上,連結串列的數量成倍減少:level0的結構與普通的單向連結串列相同,存放所有元素;level1存放總數的1/2的資料元素;level2存放總數的1/4的資料元素;level3存放總數的1/8的資料元素。
- 底層的元素總是比上層元素多,並且如果上層存在某一個元素,那麼在同一索引位置的下面幾層也都存在該元素。比如level2層存在元素4,那麼level0和level1也必定存在元素4。
基本操作
查詢操作
跳躍表按照自頂向下的順序進行查詢,也就是從head3開始查詢。
- 假設要查詢8,head3.next=8,ok!
- 假設要查詢7,head3.next=8>7,則指標向下移動到head2,head2.next=4<8,則指標向右移動到4節點,4.next=8>7,指標向下移動到level1層的4節點,4.next=6<7,指標向右移到6節點,6.next=8>7,指標向下移動到level0所在的6節點,6.next=7,ok!(查詢軌跡見下圖)
可以看出,跳躍表查詢操作的思路與二分法較為類似,相比於單向連結串列逐個遍歷要更快些。
刪除操作
在查詢操作的基礎上,只需要找到待刪除節點,然後逐個調整每一層待刪除節點前一個節點的next指標即可。
新增操作
新增操作相對複雜一些。首先要將元素新增到level0層相應的位置,而level1、level2和level3層是否新增該元素,我們採用基於概率的方法進行判斷。首先level0層肯定要新增元素,因為它要包含所有元素。規定新增到每層的概率是1/2。也就是說新增到level1層的概率是1/2,新增到level2層的概率是1/4,新增到level3層的概率是1/8......但是需要記住,如果在第n層沒有新增元素,那麼在這之上的所有層都不再新增元素。
既然是基於概率,我們可以用隨機數進行模擬。跳躍表一共有4層,2的4次方是16,利用隨機函式生成一個[0,15)之間的隨機整數,如果該隨機數在[0,8)之間,在將元素新增到level1;如果該隨機數在[0,4)之間,在將元素新增到level1、level2;如果該隨機數在[0,1)之間,在將元素新增到level1、level2、level3。
程式碼實現(Java)
public class SkipListNode { private int value; private SkipListNode[] next; // getters setters }
然後定義跳躍表SkipList
public class SkipList { private SkipListNode head; private int levelNum; private Random random; private Integer randomLimit; public SkipList(int levelNum) { // levelNum=4 : 0 1 2 3 this.head = new SkipListNode(null, levelNum); // head只存放指向下個節點的指標,不存放元素 this.levelNum = levelNum; this.random = new Random(); this.randomLimit = (int)Math.pow(2, levelNum - 1); } /** * 查詢元素 */ public boolean find(Integer value) throws Exception { if(value == null) throw new Exception("The value is null"); if(getNode(value) == null) return false; else return true; } /** * 新增元素 */ public void insert(Integer value) throws Exception { if(value == null) throw new Exception("The value is null"); if(find(value)) throw new Exception("The value already exists"); // 計算level1、level2...是否新增該元素 int level = levelNum - 1; int rand = random.nextInt(randomLimit); int powNum = 0; while(level > 0) { if(rand < Math.pow(2, powNum)) { break; } level--; powNum++; } SkipListNode newNode = new SkipListNode(value, levelNum); SkipListNode preNode = null; SkipListNode curNode = null; SkipListNode tmpNode = null; while(level >= 0) { preNode = head; curNode = head.getNext(level); while(curNode != null) { if(value < curNode.getValue()) { break; } preNode = curNode; curNode = curNode.getNext(level); } tmpNode = preNode.getNext(level); preNode.setNext(level, newNode); newNode.setNext(level, tmpNode); level--; } } // 刪除元素 public void delete(Integer value) throws Exception { if(value == null) throw new Exception("The value is null"); if(!find(value)) throw new Exception("The value doesn't exist"); int level = levelNum - 1; SkipListNode preNode = head; SkipListNode curNode = head; Integer curValue = null; while(level >= 0) { curNode = curNode.getNext(level); if(curNode != null) { curValue = curNode.getValue(); if(curValue == value) { break; } else if(curValue < value) { preNode = curNode; } else { curNode = preNode; level--; } } else { curNode = preNode; level--; } } while(level >= 0) { curNode = head.getNext(level); preNode = head; while(curNode.getValue() != value) { preNode = curNode; curNode = curNode.getNext(level); } preNode.setNext(level, curNode.getNext(level)); level--; } } // 列印跳躍表 public void printList() { int level = levelNum - 1; SkipListNode curNode = null; while(level >= 0) { curNode = head.getNext(level); System.out.print(level + " level: "); while(curNode != null) { System.out.print(curNode.getValue() + "-->"); curNode = curNode.getNext(level); } System.out.println("null"); level--; } } private SkipListNode getNode(Integer value) throws Exception { if(value == null) throw new Exception("The value is null"); int level = levelNum - 1; SkipListNode preNode = head; SkipListNode curNode = head; Integer curValue = null; while(level >= 0) { curNode = curNode.getNext(level); if(curNode != null) { curValue = curNode.getValue(); if(curValue == value) { return curNode; } else if(curValue < value) { preNode = curNode; } else { curNode = preNode; level--; } } else { curNode = preNode; level--; } } return null; } }