1 跳錶
- 一個有序鏈表搜索、添加、刪除的平均時間複雜度是多少?
- 數組查找的時間複雜度可以達到O(logn)是因爲數組支持隨機訪問,對於有序的數組,可以通過二分查找,達到O(logn)的效率
- 能否利用二分搜索優化有序鏈表,將搜索、添加、刪除的平均時間複雜度降低至 O(logn)?
- 鏈表沒有像數組那樣的高效隨機訪問(O(1) 時間複雜度),所以不能像有序數組那樣直接進行二分搜索優化
- 那有沒有其他辦法讓有序鏈表搜索、添加、刪除的平均時間複雜度降低至 O(logn)?
- 使用跳錶(SkipList)
- 跳錶,又叫做跳躍表、跳躍列表,在有序鏈表的基礎上增加了“跳躍”的功能。設計的初衷是爲了取代平衡樹(比如紅黑樹),跳錶類似TreeMap,都存放key-value
- Redis中 的 SortedSet、LevelDB 中的 MemTable 都用到了跳錶
- 對比平衡樹
- 跳錶的實現和維護會更加簡單
- 跳錶的搜索、刪除、添加的平均時間複雜度是 O(logn)
- 使用跳錶優化鏈表
- 跳錶的搜索
- 從頂層鏈表的首元素開始,從左往右搜索,直至找到一個大於或等於目標的元素,或者到達當前層鏈表的尾部
- 如果該元素等於目標元素,則表明該元素已被找到
- 如果該元素大於目標元素或已到達鏈表的尾部,則退回到當前層的前一個元素,然後轉入下一層進行搜索
- 跳錶的添加、刪除
- 本質就是在搜索的基礎上,建立一個數組,存放要添加或刪除的節點的所有層的前驅節點
- 新添加節點的層數,是隨機以一定規則獲得的
- 最後還需要考慮層數的更新
- 跳錶的層數
- 跳錶是按層構造的,底層是一個普通的有序鏈表,高層相當於是低層的“快速通道”
- 在第 i 層中的元素按某個固定的概率 p(通常爲 ½ 或 ¼ )出現在第 i + 1層中,產生越高的層數,概率越低
- 元素層數恰好等於 1 的概率爲 1 – p
- 元素層數大於等於 2 的概率爲 p,而元素層數恰好等於 2 的概率爲 p * (1 – p)
- 元素層數大於等於 3 的概率爲 p^2,而元素層數恰好等於 3 的概率爲 (p ^2) * (1 – p)
- 將這些概率加和,除以層數,得到一個元素的平均層數是 1 / (1 – p)
- 當 p = ½ 時,每個元素所包含的平均指針數量是 2
- 當 p = ¼ 時,每個元素所包含的平均指針數量是 1.33
- 跳錶中的指針數指的就是nexts數組中元素個數,而紅黑樹每個節點上指針至少是三個,next、parent、right,因此當p=1/4可以發現,其空間複雜度要好於紅黑樹
- 跳錶的複雜度分析
- 每一層的元素數量
- 第 1 層鏈表固定有 n 個元素(最底層,一定是和總元素個數相同)
- 第 2 層鏈表平均有 n * p 個元素
- 第 3 層鏈表平均有 n * p^2 個元素
- 第 k 層鏈表平均有 n * p^k 個元素
- 跳錶最高有 log (1/p) (n)層,搜索時,每一層鏈表的預期查找步數最多是 1/p
- 所以,如果p是1/4,那麼總的查找步數是最高層數*每層最多步數,即log4(n)/4,
- 因此時間複雜度爲O(logn)
- SkipList
package com.mj;
import java.util.Comparator;
@SuppressWarnings("unchecked")
public class SkipList<K, V> {
private static final int MAX_LEVEL = 32;
private static final double P = 0.25;
private int size;
private Comparator<K> comparator;
private int level;
private Node<K, V> first;
public SkipList(Comparator<K> comparator) {
this.comparator = comparator;
first = new Node<>(null, null, MAX_LEVEL);
}
public SkipList() {
this(null);
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public V get(K key) {
keyCheck(key);
Node<K, V> node = first;
for (int i = level - 1; i >= 0; i--) {
int cmp = -1;
while (node.nexts[i] != null
&& (cmp = compare(key, node.nexts[i].key)) > 0) {
node = node.nexts[i];
}
if (cmp == 0) return node.nexts[i].value;
}
return null;
}
public V put(K key, V value) {
keyCheck(key);
Node<K, V> node = first;
Node<K, V>[] prevs = new Node[level];
for (int i = level - 1; i >= 0; i--) {
int cmp = -1;
while (node.nexts[i] != null
&& (cmp = compare(key, node.nexts[i].key)) > 0) {
node = node.nexts[i];
}
if (cmp == 0) {
V oldV = node.nexts[i].value;
node.nexts[i].value = value;
return oldV;
}
prevs[i] = node;
}
int newLevel = randomLevel();
Node<K, V> newNode = new Node<>(key, value, newLevel);
for (int i = 0; i < newLevel; i++) {
if (i >= level) {
first.nexts[i] = newNode;
} else {
newNode.nexts[i] = prevs[i].nexts[i];
prevs[i].nexts[i] = newNode;
}
}
size++;
level = Math.max(level, newLevel);
return null;
}
public V remove(K key) {
keyCheck(key);
Node<K, V> node = first;
Node<K, V>[] prevs = new Node[level];
boolean exist = false;
for (int i = level - 1; i >= 0; i--) {
int cmp = -1;
while (node.nexts[i] != null
&& (cmp = compare(key, node.nexts[i].key)) > 0) {
node = node.nexts[i];
}
prevs[i] = node;
if (cmp == 0) exist = true;
}
if (!exist) return null;
Node<K, V> removedNode = node.nexts[0];
size--;
for (int i = 0; i < removedNode.nexts.length; i++) {
prevs[i].nexts[i] = removedNode.nexts[i];
}
int newLevel = level;
while (--newLevel >= 0 && first.nexts[newLevel] == null) {
level = newLevel;
}
return removedNode.value;
}
private int randomLevel() {
int level = 1;
while (Math.random() < P && level < MAX_LEVEL) {
level++;
}
return level;
}
private void keyCheck(K key) {
if (key == null) {
throw new IllegalArgumentException("key must not be null.");
}
}
private int compare(K k1, K k2) {
return comparator != null
? comparator.compare(k1, k2)
: ((Comparable<K>)k1).compareTo(k2);
}
private static class Node<K, V> {
K key;
V value;
Node<K, V>[] nexts;
public Node(K key, V value, int level) {
this.key = key;
this.value = value;
nexts = new Node[level];
}
@Override
public String toString() {
return key + ":" + value + "_" + nexts.length;
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("一共" + level + "層").append("\n");
for (int i = level - 1; i >= 0; i--) {
Node<K, V> node = first;
while (node.nexts[i] != null) {
sb.append(node.nexts[i]);
sb.append(" ");
node = node.nexts[i];
}
sb.append("\n");
}
return sb.toString();
}
}