跳表(skip list)java實現

跳表(skip list)java實現

WiKi

【轉載】http://blog.csdn.net/brillianteagle/article/details/52206261

Skip list是一個用于有序元素序列快速搜索的數據結構,由美國計算機科學家William Pugh發明于1989年。它的效率和紅黑樹以及 AVL 樹不相上下,但實現起來比較容易。作者William Pugh是這樣介紹Skip list的:
Skip lists are a probabilistic data structure that seem likely to supplant balanced trees as the implementation method of choice for many applications. Skip list algorithms have the same asymptotic expected time bounds as balanced trees and are simpler, faster and use less space.
Skip list是一個“概率型”的數據結構,可以在很多應用場景中替代平衡樹。Skip list算法與平衡樹相比,有相似的漸進期望時間邊界,但是它更簡單,更快,使用更少的空間。
Skip list是一個分層結構多級鏈表,最下層是原始的鏈表,每個層級都是下一個層級的“高速跑道”。


這里寫圖片描述

跳躍的表的性質包括:
某個i層的元素,出現在i+1層的概率p是固定的,例如常取p=1/2或p=1/4;
平均來講,每個元素出現在1/(1-p)個鏈表中;
最高的元素,例如head通常采用Int.MIN_VALUE作為的最小值,會出現在每一層鏈表中;

原始的鏈表元素如果是n,則鏈表最多
這里寫圖片描述
層。例如,p=1/2時,層數為
這里寫圖片描述

跳躍表的空間復雜度為O(n),插入刪除的時間復雜度是
這里寫圖片描述
。例如,p=1/2時,復雜度為
這里寫圖片描述

相關資料

Skip list的維基百科
《算法導論》網易公開課Skip list

跳躍表的構建

這里描述一下網易公開課《算法導論》“跳躍表”這一節的內容。
空的跳躍表頭尾相連的雙向鏈表。

這里寫圖片描述

向鏈表中放入key-value,假如key是1。


這里寫圖片描述

此時,不斷投擲硬幣,如果是反面,則不提升該節點,停止投擲硬幣;否則不斷提升該節點,并且垂直方向進行節點連接。


這里寫圖片描述

如果上一步沒有提升,再插入key-value,key等于2,然后不斷投擲硬幣,發現是投了一次正面,需要提升一次。但第二次投的是反面,不再提升。
這里寫圖片描述

再插入key-value,key等于3,然后不斷投擲硬幣,發現第一次就投了反面,不提升。
這里寫圖片描述

這樣的規則一直持續下去。由于連續投正面的概率是0.5,0.5*0.5……,所以某一個節點提升很多層的概率是很低的。這也是為什么說跳躍表是一種概率型數據結構的來源。
跳躍表的查詢也比較簡單。例如要查找key是3的節點,則從最上層開始查找,直到找到大于或等于3的位置,然后返回上一個節點,再往下一層繼續向右尋找。例如三層的跳躍表查詢路徑如下。


這里寫圖片描述

這樣,就跳過了很多節點,所以叫做“跳躍表”。

Java實現

這里參考了“跳躍表(Skip List)-實現(Java)”,將其更改為模版形式,并多處進行了重構。
節點類

/**
 * 跳躍表的節點,包括key-value和上下左右4個指針
 * created by 曹艷豐,2016-08-14
 * 參考:http://www.acmerblog.com/skip-list-impl-java-5773.html
 * */
public class SkipListNode <T>{
    public int key;
    public 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) {
        // TODO Auto-generated constructor stub
        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() {
        // TODO Auto-generated method stub
        return "key-value:"+key+"-"+value;
    }
}

跳躍表實現。

import java.util.Random;

/**
 * 不固定層級的跳躍表
 * created by 曹艷豐,2016-08-14
 * 參考:http://www.acmerblog.com/skip-list-impl-java-5773.html
 * */
public class SkipList <T>{
    private SkipListNode<T> head,tail;
    private int nodes;//節點總數
    private int listLevel;//層數
    private Random random;// 用于投擲硬幣
    private static final double PROBABILITY=0.5;//向上提升一個的概率
    public SkipList() {
        // TODO Auto-generated constructor stub
        random=new Random();
        clear();
    }
    /**
     *清空跳躍表
     * */
    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;
    }
    /**
     * 在最下面一層,找到要插入的位置前面的那個key
     * */
    private SkipListNode<T> findNode(int key){
        SkipListNode<T> p=head;
        while(true){
            while (p.right.key!=SkipListNode.TAIL_KEY&&p.right.key<=key) {
                p=p.right;
            }
            if (p.down!=null) {
                p=p.down;
            }else {
                break;
            }

        }
        return p;
    }
    /**
     * 查找是否存儲key,存在則返回該節點,否則返回null
     * */
    public SkipListNode<T> search(int key){
        SkipListNode<T> p=findNode(key);
        if (key==p.getKey()) {
            return p;
        }else {
            return null;
        }
    }
    /**
     * 向跳躍表中添加key-value
     * 
     * */
    public void put(int k,T v){
        SkipListNode<T> p=findNode(k);
        //如果key值相同,替換原來的vaule即可結束
        if (k==p.getKey()) {
            p.value=v;
            return;
        }
        SkipListNode<T> q=new SkipListNode<T>(k, v);
        backLink(p, q);
        int currentLevel=0;//當前所在的層級是0
        //拋硬幣
        while (random.nextDouble()<PROBABILITY) {
            //如果超出了高度,需要重新建一個頂層
            if (currentLevel>=listLevel) {
                listLevel++;
                SkipListNode<T> p1=new SkipListNode<T>(SkipListNode.HEAD_KEY, null);
                SkipListNode<T> p2=new SkipListNode<T>(SkipListNode.TAIL_KEY, null);
                horizontalLink(p1, p2);
                vertiacallLink(p1, head);
                vertiacallLink(p2, tail);
                head=p1;
                tail=p2;
            }
            //將p移動到上一層
            while (p.up==null) {
                p=p.left;
            }
            p=p.up;

            SkipListNode<T> e=new SkipListNode<T>(k, null);//只保存key就ok
            backLink(p, e);//將e插入到p的后面
            vertiacallLink(e, q);//將e和q上下連接
            q=e;
            currentLevel++;
        }
        nodes++;//層數遞增
    }
    //node1后面插入node2
    private void backLink(SkipListNode<T> node1,SkipListNode<T> node2){
        node2.left=node1;
        node2.right=node1.right;
        node1.right.left=node2;
        node1.right=node2;
    }
    /**
     * 水平雙向連接
     * */
    private void horizontalLink(SkipListNode<T> node1,SkipListNode<T> node2){
        node1.right=node2;
        node2.left=node1;
    }
    /**
     * 垂直雙向連接
     * */
    private void vertiacallLink(SkipListNode<T> node1,SkipListNode<T> node2){
        node1.down=node2;
        node2.up=node1;
    }
    /**
     * 打印出原始數據
     * */
    @Override
    public String toString() {
        // TODO Auto-generated method stub
        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);
            builder.append("\n");
            p=p.right;
        }

        return builder.toString();
    }

}

下面進行一下測試。

public class Main {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        SkipList<String> list=new SkipList<String>();
        System.out.println(list);
        list.put(2, "yan");
        list.put(1, "co");
        list.put(3, "feng");
        list.put(1, "cao");//測試同一個key值
        list.put(4, "曹");
        list.put(6, "豐");
        list.put(5, "艷");
        System.out.println(list);
        System.out.println(list.size());
    }
}

Java中的跳躍表

Java API中提供了支持并發操作的跳躍表ConcurrentSkipListSet和ConcurrentSkipListMap。
有序的情況下:
? 在非多線程的情況下,應當盡量使用TreeMap(紅黑樹實現)。
? 對于并發性相對較低的并行程序可以使用Collections.synchronizedSortedMap將TreeMap進行包裝,也可以提供較好的效率。但是對于高并發程序,應當使用ConcurrentSkipListMap。
無序情況下:
? 并發程度低,數據量大時,ConcurrentHashMap 存取遠大于ConcurrentSkipListMap。
? 數據量一定,并發程度高時,ConcurrentSkipListMap比ConcurrentHashMap效率更高。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容