跳躍表原理與實踐

---恢復內容開始---java

 
跳躍列表是在不少應用中有可能替代平衡樹而做爲實現方法的一種數據結構。跳躍列表的算法有同平衡樹同樣的漸進的預期時間邊界,而且更簡單、更快速和使用更少的空間。----by 發明者
像是redis中有序集合就使用到了跳躍表。
場景:商品總數量有幾十萬件,對應數據庫商品表的幾十萬條記錄。須要根據不一樣字段正序或者倒敘作精確或全量查詢,並且性能有硬性要求。
 
若是是按照商品名稱精確查詢還好辦,能夠直接從數據庫查出來,最多也就上百條記錄。
若是是沒有商品名稱的全量查詢怎麼辦?總不可能把數據庫裏的全部記錄全查出來吧,並且還要支持不一樣字段的排序。
因此,只能提早在內存中存儲有序的全量商品集合,每一種排序方式都保存成獨立的集合,每次請求的時候按照請求的排序種類,返回對應的集合。
  • 好比按價格字段排序的集合:
  • 好比按銷量字段排序的集合:
商品列表是線性的,最容易表達線性結構的天然是數組和鏈表。但是,不管是數組仍是鏈表,在插入新商品的時候,都會存在性能問題。
按照商品等級排序的集合爲例,若是使用數組,插入新商品的方式以下:
若是要插入一個價格是3的商品,首先要知道這個商品應該插入的位置。使用二分查找能夠最快定位,這一步時間複雜度是O(logN)。
插入過程當中,原數組中全部大於3的商品都要右移,這一步時間複雜度是O(N)。因此整體時間複雜度是O(N)。
若是使用鏈表,插入新商品的方式以下:
 
 
 

 

若是要插入一個價格是3的商品,首先要知道這個商品應該插入的位置。鏈表沒法使用二分查找,只能和原鏈表中的節點逐一比較大小來肯定位置。這一步的時間複雜度是O(N)。
插入的過程卻是很容易,直接改變節點指針的目標,時間複雜度O(1)。所以整體的時間複雜度也是O(N)。
這對於擁有幾十萬商品的集合來講,這兩種方法顯然都太慢了。
 
所以引入一種不一樣的數據結構----跳躍表
首先,應該要了解跳躍表的性質;
  • 由不少層結構組成;
  • 每一層都是一個有序的鏈表,排列順序爲由高層到底層,都至少包含兩個鏈表節點,分別是前面的head節點和後面的nil節點;
  • 最底層的鏈表包含了全部的元素;
  • 若是一個元素出如今某一層的鏈表中,那麼在該層之下的鏈表也全都會出現(上一層的元素是當前層的元素的子集);
  • 鏈表中的每一個節點都包含兩個指針,一個指向同一層的下一個鏈表節點,另外一個指向下一層的同一個鏈表節點;
所以在前文中的商品列表排序中,能夠取出若干關鍵節點,在新節點插入時先去比較關鍵節點,逐層下沉,最終在原鏈表中插入。這樣,比較的次數降低了一半,節點數量少的狀況下效果不明顯,但在1w甚至10w個節點的鏈表下,性能的提高會很是明顯。
 

 

搜索:
 
 

 

其基本原理就是從最高層的鏈表節點(關 鍵節點)開始,若是比當前節點要大和比當前層的下一個節點要小,那麼則往下找,也就是和當前層的下一層的節點的下一個節點進行比較,以此類推,一直找到最底層的最後一個節點,若是找到則返回,反之則返回空。
 
 
 1 find(x)   
 2 {  
 3     p = top;  
 4     while (1) {  
 5         while (p->next->key < x)  
 6             p = p->next;  
 7         if (p->down == NULL)   
 8             return p->next;  
 9         p = p->down;  
10     }  
11 } 
插入節點 :
既然要插入,首先須要肯定插入的層數,這裏有不同的方法。1. 看到一些博客寫的是拋硬幣,只要是正面就累加,直到碰見反面才中止,最後記錄正面的次數並將其做爲要添加新元素的層;2. 還有就是一些博客裏面寫的統計機率,先給定一個機率p,產生一個0到1之間的隨機數,若是這個隨機數小於p,則將高度加1,直到產生的隨機數大於機率p才中止,根據給出的結論,當機率爲1/2或者是1/4的時候,總體的性能會比較好(其實當p爲1/2的時候,也就是拋硬幣的方法)。
當肯定好要插入的層數之後,則須要將元素都插入到從最底層到第k層
拋硬幣法:

 

新節點和各層索引節點逐一比較,肯定原鏈表的插入位置。O(logN)
把索引插入到原鏈表。O(1)
利用拋硬幣的隨機方式,決定新節點是否提高爲上一級索引。結果爲「正」則提高並繼續拋硬幣,結果爲「負」則中止。O(logN)
整體上,跳躍表插入操做的時間複雜度是O(logN),而這種數據結構所佔空間是2N,既空間複雜度是 O(N)。
刪除節點
在各個層中找到包含指定值的節點,而後將節點從鏈表中刪除便可,若是刪除之後只剩下頭尾兩個節點,則刪除這一層。
 

 

自上而下,查找第一次出現節點的索引,並逐層找到每一層對應的節點。O(logN)
刪除每一層查找到的節點,若是該層只剩下1個節點,刪除整個一層(原鏈表除外)。O(logN)
整體上,跳躍表刪除操做的時間複雜度是O(logN)。
 
代碼示例:
 
 
 1 /***************************  SkipList.java  *********************/
 2 
 3 import java.util.Random;
 4 
 5 public class SkipList<T extends Comparable<? super T>> {
 6     private int maxLevel;
 7     private SkipListNode<T>[] root;
 8     private int[] powers;
 9     private Random rd = new Random();
10     SkipList() {
11         this(4);
12     }
13     SkipList(int i) {
14         maxLevel = i;
15         root = new SkipListNode[maxLevel];
16         powers = new int[maxLevel];
17         for (int j = 0; j < maxLevel; j++)
18             root[j] = null;
19         choosePowers();
20     }
21     public boolean isEmpty() {
22         return root[0] == null;
23     }
24     public void choosePowers() {
25         powers[maxLevel-1] = (2 << (maxLevel-1)) - 1;    // 2^maxLevel - 1
26         for (int i = maxLevel - 2, j = 0; i >= 0; i--, j++)
27            powers[i] = powers[i+1] - (2 << j);           // 2^(j+1)
28     }
29     public int chooseLevel() {
30         int i, r = Math.abs(rd.nextInt()) % powers[maxLevel-1] + 1;
31         for (i = 1; i < maxLevel; i++)
32             if (r < powers[i])
33                 return i-1; // return a level < the highest level;
34         return i-1;         // return the highest level;
35     }
36     // make sure (with isEmpty()) that search() is called for a nonempty list;
37     public T search(T key) { 
38         int lvl;
39         SkipListNode<T> prev, curr;            // find the highest nonnull
40         for (lvl = maxLevel-1; lvl >= 0 && root[lvl] == null; lvl--); // level;
41         prev = curr = root[lvl];
42         while (true) {
43             if (key.equals(curr.key))          // success if equal;
44                  return curr.key;
45             else if (key.compareTo(curr.key) < 0) { // if smaller, go down,
46                  if (lvl == 0)                 // if possible
47                       return null;      
48                  else if (curr == root[lvl])   // by one level
49                       curr = root[--lvl];      // starting from the
50                  else curr = prev.next[--lvl]; // predecessor which
51             }                                  // can be the root;
52             else {                             // if greater,
53                  prev = curr;                  // go to the next
54                  if (curr.next[lvl] != null)   // non-null node
55                       curr = curr.next[lvl];   // on the same level
56                  else {                        // or to a list on a lower level;
57                       for (lvl--; lvl >= 0 && curr.next[lvl] == null; lvl--);
58                       if (lvl >= 0)
59                            curr = curr.next[lvl];
60                       else return null;
61                  }
62             }
63         }
64     }
65     public void insert(T key) {
66         SkipListNode<T>[] curr = new SkipListNode[maxLevel];
67         SkipListNode<T>[] prev = new SkipListNode[maxLevel];
68         SkipListNode<T> newNode;
69         int lvl, i;
70         curr[maxLevel-1] = root[maxLevel-1];
71         prev[maxLevel-1] = null;
72         for (lvl = maxLevel - 1; lvl >= 0; lvl--) {
73             while (curr[lvl] != null && curr[lvl].key.compareTo(key) < 0) { 
74                 prev[lvl] = curr[lvl];           // go to the next
75                 curr[lvl] = curr[lvl].next[lvl]; // if smaller;
76             }
77             if (curr[lvl] != null && key.equals(curr[lvl].key)) // don't 
78                 return;                          // include duplicates;
79             if (lvl > 0)                         // go one level down
80                 if (prev[lvl] == null) {         // if not the lowest
81                       curr[lvl-1] = root[lvl-1]; // level, using a link
82                       prev[lvl-1] = null;        // either from the root
83                 }
84                 else {                           // or from the predecessor;
85                      curr[lvl-1] = prev[lvl].next[lvl-1];
86                      prev[lvl-1] = prev[lvl];
87                 }
88         }
89         lvl = chooseLevel();                // generate randomly level 
90         newNode = new SkipListNode<T>(key,lvl+1); // for newNode;
91         for (i = 0; i <= lvl; i++) {        // initialize next fields of
92             newNode.next[i] = curr[i];      // newNode and reset to newNode
93             if (prev[i] == null)            // either fields of the root
94                  root[i] = newNode;         // or next fields of newNode's
95             else prev[i].next[i] = newNode; // predecessors;
96         }
97     }
98 }

 

有的時候跳躍表經常使用來代替紅黑樹,由於跳躍表在維持結構平衡時成本較低,徹底靠隨機,而紅黑樹這一類查找樹每一次添加刪除後都須要rebalance。

 

 

 

 

 

 

 
 
 
 
 

---恢復內容結束---node

相關文章
相關標籤/搜索