跳躍表原理與實踐
阿新 • • 發佈:2018-06-21
比較大小 兩種 恢復 使用數組 線性結構 -type top equal this
---恢復內容開始---
- 參考自 http://blog.jobbole.com/111731/
- 比如按價格字段排序的集合:
- 比如按銷量字段排序的集合:
如果要插入一個價格是3的商品,首先要知道這個商品應該插入的位置。鏈表無法使用二分查找,只能和原鏈表中的節點逐一比較大小來確定位置。這一步的時間復雜度是O(N)。 插入的過程倒是很容易,直接改變節點指針的目標,時間復雜度O(1)。因此總體的時間復雜度也是O(N)。 這對於擁有幾十萬商品的集合來說,這兩種方法顯然都太慢了。 因此引入一種不同的數據結構----跳躍表 首先,應該要了解跳躍表的性質;
- 由很多層結構組成;
- 每一層都是一個有序的鏈表,排列順序為由高層到底層,都至少包含兩個鏈表節點,分別是前面的head節點和後面的nil節點;
- 最底層的鏈表包含了所有的元素;
- 如果一個元素出現在某一層的鏈表中,那麽在該層之下的鏈表也全都會出現(上一層的元素是當前層的元素的子集);
- 鏈表中的每個節點都包含兩個指針,一個指向同一層的下一個鏈表節點,另一個指向下一層的同一個鏈表節點;
搜索:
其基本原理就是從最高層的鏈表節點(關 鍵節點)開始,如果比當前節點要大和比當前層的下一個節點要小,那麽則往下找,也就是和當前層的下一層的節點的下一個節點進行比較,以此類推,一直找到最底層的最後一個節點,如果找到則返回,反之則返回空。
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。
---恢復內容結束---
跳躍表原理與實踐