在查找算法的解決方案中,即根據 key 來查找其所在的位置,主要思想通常是基於兩種,一種是基於平衡樹,還有一種是基於哈希表。java
而跳躍表(Skip List,下文簡稱跳錶),也能夠理解爲查找算法的解決方案之一,可是它卻無法歸類到上述兩種方案中,而且跳錶實現起來也是比較簡單的,在大部分應用場景下,跳錶的性能是和平衡樹相差無幾的。node
跳躍表這種數據結構是由 William Pugh 發明的,首次公開出現於他的論文中【下載】,該種數據結構是一種很精妙的設計。git
跳躍表(Skip List),顧名思義,首先它是一個 list,是一個基於鏈表而改進的數據結構。衆所周知,鏈表的優點是增刪,而不是查詢,由於鏈表中每一個節點都會記錄而且只會記錄下一個節點,因此在鏈表中查找數據時,是須要從首個數據開始挨個進行查找的(時間複雜度爲O(n))。而跳錶的優點在於,查找數據時是不會挨個進行查找的,能夠抽象理解爲它是會按照某些規則跳過部分節點來進行查找的,因此查詢的性能是高於鏈表的。github
以下的簡圖,目標是查找77:redis
從上面的初窺中能夠看出,咱們要查找一個值的時候,並不須要像鏈表同樣挨個查找,而是在期間跳過了部分節點,從而在性能上獲得了提高。算法
另外上圖中體現了一個 高度 的概念,如上面的第二步,咱們從20開始向後查找到的節點是80而不是77,由於當時咱們處於20的第三層高度,而第三層高度是指向80的,因此咱們只會查找到80,可是對比發現80嫌大,因此便往下退一層,即第二層,而第二層也是指向80的,再向下到達了第一層,第一層指向的節點是77,此時正好找到了目標值。數據結構
下圖展現了每層的關聯關係: app
其實從某種意義上來講,跳錶和二分查找仍是有些些類似的,跳錶的時間複雜度爲O(log n)。dom
上文介紹了跳錶的基本思路,可是是基於查詢的,那麼跳錶是如何一步步造成的呢?換句話說,數據是如何插入的呢?ide
其實上文有一個概念沒有講清楚,即每一個節點的層高是如何產生的,至因而如何產生的,偷偷告訴你,是在必定限制下隨機生成的,哈哈哈,驚不驚喜,意不意外。
下面畫圖介紹了一下數據的插入過程。
在插入一個數據的時候,會影響先後相關的關聯索引,主要會影響第一層至當前層的關聯索引。
在上圖中,若是咱們要查找30,則過程見下圖中帶序號的虛線:
固然,在新增一個節點的時候,確定也是要先進行一下查詢動做的。
而刪除節點,也是和新增的過程差很少,也是須要調整先後數據的關聯索引的。
上面提到的層數是一個隨機數,可是是在必定的限制範圍以內的。
關於此處的限制,主要設計到兩個概念:
在 Redis 的 SkipList 中,默認的 p 爲 1/4,默認的 MaxLevel 爲 32
獲取層高的僞代碼爲:
function getRandomLevel() {
int level = 1;
while(random() < p && level < MaxLevel) {
level ++;
}
return level;
}
複製代碼
至於具體的細節,建議參考 Redis 中的跳躍表,暫不贅述,此處僅用 Java 語言來大概實現一下跳躍表。
/** * @Description: 跳躍表 * @Author: Jet.Chen * @Date: 2019/9/16 17:39 */
public class SkipList <T>{
private SkipListNode<T> head,tail;
private int nodes; // 節點總數
private int listLevel; // 最大層數
private Random random; // 隨機數,用於投擲硬幣決定是否要加層高
private static final double PROBABILITY = 0.25; // 向上提高一個的機率(此處採用redis中的默認值)
private static final int MAXLEVEL = 32; // 最大層高(此處採用redis中的默認值)
public SkipList() {
random = new Random();
clear();
}
/** * @Description: 清空跳躍表 * @Param: [] * @return: void * @Author: Jet.Chen * @Date: 2019/9/16 17:41 */
public void clear(){
head = new SkipListNode<T>(SkipListNode.HEAD_KEY, null);
tail = new SkipListNode<T>(SkipListNode.TAIL_KEY, null);
horizontalLink(head, tail);
listLevel = 0;
nodes = 0;
}
public boolean isEmpty(){
return nodes == 0;
}
public int size() {
return nodes;
}
/** * @Description: 找到要插入的位置前面的那個key 的最底層節點 * @Param: [key] * @return: com.jet.SkipListNode<T> * @Author: Jet.Chen * @Date: 2019/9/16 17:42 */
private SkipListNode<T> findNode(int key){
SkipListNode<T> p = head;
while(true){
while (p.right.getKey() != SkipListNode.TAIL_KEY && p.right.getKey() <= key) {
p = p.right;
}
if (p.down != null) {
p = p.down;
} else {
break;
}
}
return p;
}
/** * @Description: 查找是否存在key,存在則返回該節點,不然返回null * @Param: [key] * @return: com.wailian.SkipListNode<T> * @Author: Jet.Chen * @Date: 2019/9/16 17:43 */
public SkipListNode<T> search(int key){
SkipListNode<T> p = findNode(key);
if (key == p.getKey()) {
return p;
} else {
return null;
}
}
/** * @Description: 向跳躍表中添加key-value * @Param: [k, v] * @return: void * @Author: Jet.Chen * @Date: 2019/9/16 17:43 */
public void put(int k,T v){
SkipListNode<T> p = findNode(k);
// 若是key值相同,替換原來的value便可結束
if (k == p.getKey()) {
p.setValue(v);
return;
}
SkipListNode<T> q = new SkipListNode<>(k, v);
backLink(p, q);
int currentLevel = 0; // 當前所在的層級是0
// 拋硬幣
while (random.nextDouble() < PROBABILITY && currentLevel < MAXLEVEL) {
// 若是超出了高度,須要從新建一個頂層
if (currentLevel >= listLevel) {
listLevel++;
SkipListNode<T> p1 = new SkipListNode<>(SkipListNode.HEAD_KEY, null);
SkipListNode<T> p2 = new SkipListNode<>(SkipListNode.TAIL_KEY, null);
horizontalLink(p1, p2);
verticalLink(p1, head);
verticalLink(p2, tail);
head = p1;
tail = p2;
}
// 將p移動到上一層
while (p.up == null) {
p = p.left;
}
p = p.up;
SkipListNode<T> e = new SkipListNode<>(k, null); // 只保存key就ok
backLink(p, e); // 將e插入到p的後面
verticalLink(e, q); // 將e和q上下鏈接
q = e;
currentLevel++;
}
nodes++; // 層數遞增
}
/** * @Description: node1後面插入node2 * @Param: [node1, node2] * @return: void * @Author: Jet.Chen * @Date: 2019/9/16 17:45 */
private void backLink(SkipListNode<T> node1,SkipListNode<T> node2){
node2.left = node1;
node2.right = node1.right;
node1.right.left = node2;
node1.right = node2;
}
/** * @Description: 水平雙向鏈接 * @Param: [node1, node2] * @return: void * @Author: Jet.Chen * @Date: 2019/9/16 17:45 */
private void horizontalLink(SkipListNode<T> node1,SkipListNode<T> node2){
node1.right = node2;
node2.left = node1;
}
/** * @Description: 垂直雙向鏈接 * @Param: [node1, node2] * @return: void * @Author: Jet.Chen * @Date: 2019/9/16 17:45 */
private void verticalLink(SkipListNode<T> node1, kipListNode<T> node2){
node1.down = node2;
node2.up = node1;
}
@Override
public String toString() {
if (isEmpty()) {
return "跳躍表爲空!";
}
StringBuilder builder = new StringBuilder();
SkipListNode<T> p=head;
while (p.down != null) {
p = p.down;
}
while (p.left != null) {
p = p.left;
}
if (p.right!= null) {
p = p.right;
}
while (p.right != null) {
builder.append(p).append("\n");
p = p.right;
}
return builder.toString();
}
}
複製代碼
/** * @Description: 跳躍表的節點,包括key-value和上下左右4個指針 * @Author: Jet.Chen * @Date: 2019/9/16 17:48 */
public class SkipListNode <T>{
private int key;
private T value;
public SkipListNode<T> up, down, left, right; // 上下左右 四個指針
public static final int HEAD_KEY = Integer.MIN_VALUE; // 負無窮
public static final int TAIL_KEY = Integer.MAX_VALUE; // 正無窮
public SkipListNode(int k, T v) {
key = k;
value = v;
}
public int getKey() {
return key;
}
public void setKey(int key) {
this.key = key;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null) {
return false;
}
if (!(o instanceof SkipListNode<?>)) {
return false;
}
SkipListNode<T> ent;
try {
ent = (SkipListNode<T>) o; // 檢測類型
} catch (ClassCastException ex) {
return false;
}
return (ent.getKey() == key) && (ent.getValue() == value);
}
@Override
public String toString() {
return "key-value:" + key + "-" + value;
}
}
複製代碼
public class Main {
public static void main(String[] args) {
SkipList<String> list = new SkipList<>();
System.out.println(list);
list.put(6, "cn");
list.put(1, "https");
list.put(2, ":");
list.put(3, "//");
list.put(1, "http");
list.put(4, "jetchen");
list.put(5, ".");
System.out.println(list);
System.out.println(list.size());
}
}
複製代碼