先貼上一個MIT跳躍表公開課連接:http://open.163.com/movie/2010/12/7/S/M6UTT5U0I_M6V2TTJ7S.htmlhtml
redis中的有序鏈表結構就是在跳躍表的基礎上實現的。詳細的能夠參考http://blog.csdn.net/acceptedxukai/article/details/17333673java
個人實現方法是,最左側使用數值的最小值(Double.MIN_VALUE)看成下界。所以,規定存儲的值至少大於該值。redis
下面是跳躍表的圖例數據結構
包含 分值score,val,以及next和down指針app
/** * 跳躍表的節點的構成 * * @param <E> */ private static class SkipNode<E> { E val;//存儲的數據 double score;//跳躍表按照這個分數值進行從小到大排序。 SkipNode<E> next, down;//next指針,指向下一層的指針 SkipNode() { } SkipNode(E val, double score) { this.val = val; this.score = score; } }
package com.ljd.skiplist; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * Created by author on 2017/10/9. * 實現跳躍表:可以對遞增鏈表實現logN的查詢時間 */ public class SkipList<T> { public static void main(String[] args) { //測試隨機數生成的結果對二取模,結果是不是接近於0.5 // Random r = new Random(47); // int t = 1, a = 1; // while (a < 10000000) { // a++; // if (r.nextInt() % 2 == 0) // t++; // } // System.out.println(t * 1.0 / a); SkipList<String> list = new SkipList<>(); list.put(1.0, "1.0"); System.out.println(list); list.put(2.0, "2.0"); System.out.println(list); list.put(3.0, "3.0"); System.out.println(list); list.put(4.0, "4.0"); System.out.println(list); list.put(4.0, "5.0"); System.out.println(list); list.delete(3.0); list.delete(3.5); System.out.println(list); System.out.println("查找4.0" + list.get(4.0)); } /** * 跳躍表的節點的構成 * * @param <E> */ private static class SkipNode<E> { E val;//存儲的數據 double score;//跳躍表按照這個分數值進行從小到大排序。 SkipNode<E> next, down;//next指針,指向下一層的指針 SkipNode() { } SkipNode(E val, double score) { this.val = val; this.score = score; } } private static final int MAX_LEVEL = 1 << 6; //跳躍表數據結構 private SkipNode<T> top; private int level = 0; //用於產生隨機數的Random對象 private Random random = new Random(); public SkipList() { //建立默認初始高度的跳躍表 this(4); } //跳躍表的初始化 public SkipList(int level) { this.level = level; int i = level; SkipNode<T> temp = null; SkipNode<T> prev = null; while (i-- != 0) { temp = new SkipNode<T>(null, Double.MIN_VALUE); temp.down = prev; prev = temp; } top = temp;//頭節點 } /** * 產生節點的高度。使用拋硬幣 * * @return */ private int getRandomLevel() { int lev = 1; while (random.nextInt() % 2 == 0) lev++; return lev > MAX_LEVEL ? MAX_LEVEL : lev; } /** * 查找跳躍表中的一個值 * * @param score * @return */ public T get(double score) { SkipNode<T> t = top; while (t != null) { if (t.score == score) return t.val; if (t.next == null) { if (t.down != null) { t = t.down; continue; } else return null; } if (t.next.score > score) { t = t.down; } else t = t.next; } return null; } public void put(double score, T val) { //1,找到須要插入的位置 SkipNode<T> t = top, cur = null;//若cur不爲空,表示當前score值的節點存在 List<SkipNode<T>> path = new ArrayList<>();//記錄每一層當前節點的前驅節點 while (t != null) { if (t.score == score) { cur = t; break;//表示存在該值的點,表示須要更新該節點 } if (t.next == null) { path.add(t);//須要向下查找,先記錄該節點 if (t.down != null) { t = t.down; continue; } else { break; } } if (t.next.score > score) { path.add(t);//須要向下查找,先記錄該節點 if (t.down == null) { break; } t = t.down; } else t = t.next; } if (cur != null) { while (cur != null) { cur.val = val; cur = cur.down; } } else {//當前表中不存在score值的節點,須要從下到上插入 int lev = getRandomLevel(); if (lev > level) {//須要更新top這一列的節點數量,同時須要在path中增長這些新的首節點 SkipNode<T> temp = null; SkipNode<T> prev = top;//前驅節點如今是top了 while (level++ != lev) { temp = new SkipNode<T>(null, Double.MIN_VALUE); path.add(0, temp);//加到path的首部 temp.down = prev; prev = temp; } top = temp;//頭節點 level = lev;//level長度增長到新的長度 } //從後向前遍歷path中的每個節點,在其後面增長一個新的節點 SkipNode<T> downTemp = null, temp = null, prev = null; // System.out.println("當前深度爲"+level+",當前path長度爲"+path.size()); for (int i = level - 1; i >= level - lev; i--) { temp = new SkipNode<T>(val, score); prev = path.get(i); temp.next = prev.next; prev.next = temp; temp.down = downTemp; downTemp = temp; } } } /** * 根據score的值來刪除節點。 * * @param score */ public void delete(double score) { //1,查找到節點列的第一個節點的前驅 SkipNode<T> t = top; while (t != null) { if (t.next == null) { t = t.down; continue; } if (t.next.score == score) {
// 在這裏說明找到了該刪除的節點
t.next = t.next.next;
t = t.down;
//刪除當前節點後,還須要繼續查找以後須要刪除的節點
continue;
} if (t.next.score > score) t = t.down; else t = t.next; } } @Override public String toString() { StringBuilder sb = new StringBuilder(); SkipNode<T> t = top, next = null; while (t != null) { next = t; while (next != null) { sb.append(next.score + " "); next = next.next; } sb.append("\n"); t = t.down; } return sb.toString(); } }
對於插入的時候,在尋找插入位置的同時,我使用了一個ArrayList存儲查找方向向下時的節點。這樣在構造節點的時候,只須要直接從這個list中拿prev節點就好了。dom
下面這種方式的實現,比上面的少消耗了不少存val的空間,這個後續看可否實現。ide