跳躍表的基本概念以及代碼實現詳解

一 簡介

(1)什麼是跳躍表?

所謂跳躍表,就是在普通鏈表的基礎上增長了多層索引鏈表,這樣在查找時就能夠經過在上下不一樣層級的索引鏈表間跳躍,以達到快速查找的目的。java

固然,我這樣說可能比較抽象,下面我用一張圖來簡單解釋一下。node

跳躍表示例

從上面這張圖能夠看出,跳躍表有三種不一樣的對象。git

i)以藍色表示的數據鏈表:

圖中最下層是真正存儲數據的數據鏈表,其中每一個Node節點都有三個字段:數組

  • key:鍵值對的KEY
  • value:鍵值對的VALUE
  • next:指向下一個數據節點

此外,數據鏈表還有一個特色,那就是它是順序存儲的,經過插入數據時處理實現bash

ii)以橙色表示的索引鏈表:

經過觀察圖中的索引鏈表,咱們能夠發現它有一個特色,那就是它是分層級的,並且同時指向它的下層索引節點以及同層級的右側的索引節點。所以,每一個Index節點也有三個字段:數據結構

  • node:同一列的Index節點都指向最下層的同一個Node節點,目的是能夠隨時經過索引節點判斷出它對應的數據節點是哪一個
  • down:指向下一層的索引節點
  • right:指向同一層的右側的索引節點

iii)以紫色表示的頭部索引鏈表:

仔細觀察上面的示意圖能夠發現,頭部索引鏈表本質上也是索引鏈表,只是在普通的索引鏈表的基礎上新增了一個表示當前橫向鏈表層級的字段。所以,每一個HeadIndex節點就須要用四個字段來表示:dom

  • level:當前索引鏈表的層級,從 1 開始計數
  • node:指向左下角的基礎Node節點,這個Node節點只是起到一個標誌性做用,所以沒有KEY
  • down:指向下一層的頭部索引節點
  • right:指向同一層的右側的索引節點

(2)跳躍表的查找

仍是用上面那個示意圖舉例,咱們來看兩個典型的查找示例。ide

i)查找數據節點D:

查找數據節點D

很明顯,先從左上角的HeadIndex節點開始查找。測試

  1. 第三層右側的索引節點是Index[G],其數據節點大於D,因此不能移動到右側的Index[G]
  2. 降低到第二層的HeadIndex節點繼續向右查找,Index[B]的數據節點的KEY是B,小於D,所以移動到第二層的Index[B]
  3. 再次將第二層的Index[B]跟右側的Index[G]比較,發現不能繼續向右移動;
  4. 降低到第一層的Index[B],而後跟右側的Index[F]比較,發現不能繼續向右移動;
  5. 緊接着,下面已經沒有其餘索引鏈表,所以在數據鏈表上繼續向右查找D,最後查得返回結果。

ii)查找數據節點H:

看完上面的例子後,相信你應該已經明白,在跳躍表中查找數據通常須要如下幾個步驟:大數據

  1. 從左上角的HeadIndex節點開始查找;
  2. 而後先在橫向鏈表中向右查找小於目標節點的最大值的索引節點(PS:若是這個索引節點對應的數據節點就是咱們查找的那個節點,則直接返回);
  3. 若是執行完上面步驟後的那個索引節點還存在更底層的索引節點,那麼繼續向下移動一步;
  4. 重複上面的二、3步驟,直到下面已經沒有更底層的索引節點;
  5. 最後在數據鏈表上繼續向右查找目標節點,並返回最終結果。

根據上面敘述的通常性步驟,下面咱們經過一張示意圖再來看看查找H須要什麼步驟。

查找數據節點H

最後,我上面畫的這個示意圖已經表示得很清楚了,所以文字性敘述我就再也不多說了。

(3)跳躍表的插入

在上面的示意圖能夠看出,跳躍表主要分爲兩個部分:數據鏈表和索引鏈表。所以,在插入數據的時候首先須要作的是將數據插入到數據鏈表中,而後再給新插入的數據設置一列合理的索引

咱們這裏仍是以上面那個圖爲例,假設節點B是新插入到跳躍表的節點,咱們來看一下整個插入過程須要作哪些步驟。

跳躍表的插入

i)將數據插入到數據鏈表:

由於跳躍表是在普通鏈表的基礎上增長了多個層級的索引鏈表,以實現快速查找的目的,因此它的核心功能仍然有一個數據鏈表來存儲數據,所以咱們須要作的第一步就是將數據插入到數據鏈表合適的位置。

有了上面查詢流程的介紹,這個步驟的實現就很簡單了,總共須要如下兩步來實現:

  1. 查找比待插入節點小的最大數據節點;
  2. 將新節點插入到數據鏈表。

ii)創建隨機層級的合理的索引:

在將數據插入到數據鏈表後,咱們還須要作的是爲這個新的數據節點創建合適的索引。這一個步驟沒有標準答案,只須要爲新節點創建隨機層級的索引節點就能夠了,固然最好還能夠作到均勻分佈。

Java8ConcurrentSkipListMap源碼中,它設置的策略是:

  1. 生成一個隨機整數(多是整數,也多是負數);
  2. 判斷這個隨機數的二進制表示的最高位和最低位是否同時爲1,若是都爲1,則不須要爲該節點創建索引,不然走下面創建索引節點的流程(00、0一、十、11,所以有3/4的機率會建立索引);
  3. 設置默認待創建的索引的層級是1,而後判斷上面這個隨機數的二進制表示從右數第二位起一共有幾個1,有幾個1前面的默認層級(1)就自增幾回做爲最終的新節點的索引層級。固然,若是這個數字太大,超過了原來索引鏈表的最大層級,則將新節點的索引層級設置爲原來索引鏈表的最大層級再加一;
  4. 循環初始化新節點新增的那一列的索引節點,這個步驟結束。

iii)將新創建的索引節點關聯到原有的索引鏈表中:

在上面那個步驟完成索引節點的初始化後,這一步須要作的是將各層級的索引節點關聯到原來的橫向索引鏈表中。所以能夠經過如下四步來實現:

  1. 從左上角的HeadIndex節點開始查找;
  2. 根據level新節點的KEY找到新節點的最頂層的索引節點(PS:上面步驟完成後獲得的就是這個最頂層的索引節點)的左邊的那個索引節點,而後在這一層的橫向索引鏈表中加入新節點的最頂層的索引節點;
  3. 繼續將新節點的最頂層的索引節點的底層的索引節點插入到它所在的橫向鏈表中。一樣先是根據當前level新節點的KEY查到其左邊的那個索引節點,而後將新節點的這一層的索引節點加入到橫向索引鏈表中;
  4. 重複上面的二、3步驟,直到將新節點的全部索引節點都關聯到原來的橫向索引鏈表中。

(4)跳躍表的刪除

跳躍表的刪除實際上就是插入過程的逆向操做,所以理解了上面是怎麼插入的,天然就知道如何刪除了。固然,主要步驟也是三個:

  1. 查找到須要刪除的那個數據節點;
  2. 將數據節點從數據鏈表上移除;
  3. 循環刪除這個節點的全部層級的索引節點,並從新關聯索引節點。

二 跳躍表的Java實現

在上面,我花了很大篇幅介紹跳躍表的基本操做步驟,下面我將給出一份用Java實現的跳躍表,詳細邏輯能夠參考上述說明以及代碼中的註釋。

完整源碼地址:gitee.com/zifangsky/D…

package cn.zifangsky.skiplist;

import cn.zifangsky.hashtable.Map;

import java.text.MessageFormat;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiConsumer;

/** * 跳錶 * * @author zifangsky * @date 2019/8/8 * @since 1.0.0 */
public class SkipListMap <K extends Comparable, V> implements Map<K, V> {

    /** * 單個節點信息 */
    static class Node<K extends Comparable, V> {
        /** * key */
        final K key;
        /** * value */
        volatile Object value;
        /** * 下一個節點 */
        volatile Node<K, V> next;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
            this.next = null;
        }

        public Node(K key, Object value, Node<K, V> next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }

        /** * 返回是不是「鏈表最左下角的 BASE_HEADER 節點」 */
        boolean isBaseHeader(){
            return value == BASE_HEADER;
        }

        /** * 返回是不是數據節點 */
        V getValidValue() {
            Object v = value;
            if (v == this || this.isBaseHeader()) {
                return null;
            }

            return (V)v;
        }

        /** * 跟另外一個節點比較KEY的大小 * @param o 另外一個節點 * @return int */
        int compareKeyTo(Node<K, V> o){
            return this.key.compareTo(o.key);
        }

        /** * 當前節點KEY跟另外一個節點的KEY比較 * @param key 另外一個節點的KEY * @return int */
        int compareKeyTo(K key){
            return this.key.compareTo(key);
        }

        @Override
        public final String toString() {
            return key + "=" + value;
        }

        @Override
        public final boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            Node<?, ?> node = (Node<?, ?>) o;
            return SkipListMap.equals(key, node.key) &&
                    SkipListMap.equals(value, node.value);
        }

        /** * 取 key 和 value 的 hashCode 的異或 */
        @Override
        public final int hashCode() {
            return SkipListMap.hashCode(key) ^ SkipListMap.hashCode(value);
        }
    }

    /** * 索引節點 */
    static class Index<K extends Comparable, V> {
        /** * 索引指向的最底層的數據節點 */
        final Node<K, V> node;

        /** * 下邊的索引節點 */
        final Index<K, V> down;

        /** * 右邊的索引節點 */
        volatile Index<K, V> right;

        public Index(Node<K, V> node, Index<K, V> down) {
            this(node, down, null);
        }

        public Index(Node<K, V> node, Index<K, V> down, Index<K, V> right) {
            this.node = node;
            this.down = down;
            this.right = right;
        }
    }

    /** * 頭部索引節點 */
    static class HeadIndex<K extends Comparable, V> extends Index<K, V>{
        /** * 用於標識當前索引的層級 */
        int level;

        public HeadIndex(Node<K, V> node, Index<K, V> down, Index<K, V> right, int level) {
            super(node, down, right);
            this.level = level;
        }
    }

    /* ---------------- Fields -------------- */

    /** * 初始首節點 */
    private static final Object BASE_HEADER = new Object();

    /** * 默認第一層的level */
    public static final int DEFAULT_LEVEL = 1;

    /** * 整個跳躍表的最頂層的頭節點 */
    private transient volatile HeadIndex<K, V> head;

    /** * 索引的level */
    private volatile int level;

    public SkipListMap() {
        this.initialize();
    }

    public static int hashCode(Object o) {
        return o != null ? o.hashCode() : 0;
    }

    public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }

    @Override
    public int size() {
        int count  = 0;

        for(Node<K, V> temp = this.findFirst(); temp != null; temp = temp.next){
            if(temp.getValidValue() != null){
                count++;
            }
        }

        return count;
    }

    @Override
    public boolean isEmpty() {
        return this.findFirst() == null;
    }

    @Override
    public boolean containsKey(K key) {
        return this.getNode(key) != null;
    }

    @Override
    public V get(K key) {
        Node<K, V> temp;
        return (temp = this.getNode(key)) != null ? (V) temp.value : null;
    }

    @Override
    public void put(K key, V value) {
        this.putVal(key, value);
    }

    @Override
    public void remove(K key) {
        this.removeVal(key);
    }

    @Override
    public void clear() {
        this.initialize();
    }

    @Override
    public void forEach(BiConsumer<? super K, ? super V> action) {

    }

    /** * 格式化遍歷輸出 */
    public void forEach() {
        HeadIndex<K, V> headIdx = this.head;

        //1. 獲取索引節點的數組
        Index<?, ?>[] idxArr = new Index<?, ?>[this.level];
        while (headIdx != null){
            idxArr[headIdx.level-1] = headIdx.right;
            headIdx = (HeadIndex<K, V>) headIdx.down;
        }

        //2. 獲取第一個數據節點
        Node<K, V> node = this.findFirst();

        //3. 遍歷索引節點
        for(int i = this.level; i > 0; i--){
            System.out.print(MessageFormat.format("HeadIdx[level={0}]", i));

            if(idxArr.length == this.level){
                Index<?, ?> idx = idxArr[i - 1];
                Node<K, V> tmpNode = node;

                //以數據鏈表爲基準遍歷索引列表,若是某個數據節點不存在索引則用佔位符替代
                while (tmpNode != null){
                    if(idx != null && tmpNode.equals(idx.node)){
                        System.out.print(MessageFormat.format(", Idx[key={0}]", tmpNode.key));
                        idx = idx.right;
                    }else{
                        System.out.print(", Idx[null]");
                    }

                    tmpNode = tmpNode.next;
                }
            }

            System.out.print("\n");
        }

        //4. 遍歷數據節點
        System.out.print("HeadNode[BASE_HEADER]");
        while (node != null){
            System.out.print(MessageFormat.format(", Node[key={0}, value={1}]", node.key, node.value));

            node = node.next;
        }
        System.out.print("\n");
    }


    /** * 初始化 */
    private void initialize() {
        level = DEFAULT_LEVEL;
        head = new HeadIndex<K, V>(new Node<K, V>(null, BASE_HEADER, null),
                null, null, DEFAULT_LEVEL);
    }

    /** * 獲取第一個數據節點 */
    private Node<K, V> findFirst(){
        Node<K, V> preNode = this.head.node;
        //數據部分的節點
        Node<K, V> node = preNode.next;

        if(node != null){
            return node;
        }else{
            return null;
        }
    }

    /** * 移除某個鍵值對 */
    private void removeVal(K key){
        //1. 刪除數據節點
        //1.1 獲取key的前置數據節點
        Node<K, V> preNode = this.findPreNode(key);
        //下一個數據節點
        Node<K, V> node = preNode.next;

        //1.2 判斷是不是咱們查找的那個節點
        if(node == null || node.key.compareTo(key) != 0){
            return;
        }

        //1.3 將數據節點從鏈表上移除
        preNode.next = node.next;


        //2. 刪除索引節點
        Index<K, V> preLevelIdx = this.findDownPreIdx(this.head, node);
        if(preLevelIdx != null){
            //2.1 獲取目標節點的索引節點
            Index<K, V> levelIdx = preLevelIdx.right;

            //2.2 從新關聯索引鏈表
            preLevelIdx.right = levelIdx.right;

            //2.3 繼續刪除下層的索引節點
            while (preLevelIdx != null){
                preLevelIdx = this.findDownPreIdx(preLevelIdx, node);

                if (preLevelIdx != null) {
                    levelIdx = preLevelIdx.right;
                    preLevelIdx.right = levelIdx.right;
                }
            }
        }
    }

    /** * 根據 key 查找對應數據節點 */
    private Node<K, V> getNode(K key){
        //1. 獲取key的前置數據節點
        Node<K, V> preNode = this.findPreNode(key);
        //下一個數據節點
        Node<K, V> node = preNode.next;

        //2. 判斷是不是咱們查找的那個節點
        if(node != null && node.key.compareTo(key) == 0){
            return node;
        }else{
            return null;
        }
    }


    /** * 存儲某個鍵值對 */
    private void putVal(K key, V value){
        //1. 獲取key的前置數據節點
        Node<K, V> preNode = this.findPreNode(key);
        //獲取後置節點,做爲新節點的後置節點
        Node<K, V> nextNode = preNode.next;

        //若是發現重複節點,則直接替換值並返回
        if(nextNode != null && nextNode.compareKeyTo(key) == 0){
            nextNode.value = value;
            preNode.next = nextNode;
            return;
        }

        //2. 建立新的數據節點
        Node<K, V> newNode = new Node<>(key, value);

        //3. 將新節點掛載到鏈表
        newNode.next = nextNode;
        preNode.next = newNode;

        //4. 設置新節點的索引
        this.createNodeIndex(newNode);
    }

    /** * 設置新插入數據節點的索引狀況 * @param newNode 新插入到鏈表的數據節點 */
    private void createNodeIndex(Node<K, V> newNode){
        //1. 生成一個隨機整數
        int rnd = ThreadLocalRandom.current().nextInt();

        //2. 若是最高位和最低位都爲1,則直接插入鏈表(1/4的機率),其餘的須要建立索引節點
        if((rnd & 0x80000001) == 0){
            HeadIndex<K, V> headIdx = this.head;
            Index<K, V> idx = null;

            //索引節點的層級
            int level = 1, maxLevel = headIdx.level;

            //2.1 判斷應該創建幾層的索引節點(從右邊第2爲開始計算連續爲1的個數)
            while (((rnd = rnd >>> 1) & 1) == 1){
                level++;
            }

            //2.2 建立對應的索引節點
            //若是計算出來的層級沒有超過原來最大的層級,則直接循環創建索引節點,不然須要在頂層再新建一層
            if(level <= maxLevel){
                for(int i = 1; i <= level; i++){
                    idx = new Index<>(newNode, idx);
                }
            }else{
                //在頂層再新建一層索引節點
                level = maxLevel + 1;
                this.level = level;

                //2.2.1 建立索引節點
                for(int i = 1; i <= level; i++){
                    idx = new Index<>(newNode, idx);
                }

                //2.2.2 從新設置各層級的頭索引結點
                HeadIndex<K, V> newHeadIdx = new HeadIndex<>(headIdx.node, headIdx, null, level);
                HeadIndex<K, V> levelHeadIdx = newHeadIdx;
                for(int i = level; i >= 1; i--){
                    levelHeadIdx.level = i;
                    levelHeadIdx = (HeadIndex<K, V>) levelHeadIdx.down;
                }

                //建立新的最頂層的頭節點
                this.head = newHeadIdx;
                headIdx = newHeadIdx;
            }

            //2.3 將新建立的那一列索引節點跟原來的關聯起來
            Index<K, V> levelIdx = idx;
            Index<K, V> preLevelIdx = this.findDownPreIdx(headIdx, headIdx.level, level, idx.node);
            //橫向關聯索引節點
            levelIdx.right = preLevelIdx.right;
            preLevelIdx.right = levelIdx;

            for(int i = level; i > 1; i--){
                levelIdx = levelIdx.down;
                preLevelIdx = this.findDownPreIdx(preLevelIdx, i, (i - 1), levelIdx.node);

                //橫向關聯索引節點
                levelIdx.right = preLevelIdx.right;
                preLevelIdx.right = levelIdx;
            }
        }
    }

    /** * 查找底層的指定索引節點的前置索引節點,沒有則返回空 * @param current 當前索引位置 * @param expectedNode 目標節點 * @return cn.zifangsky.skiplist.SkipListMap.Index<K, V> */
    private Index<K, V> findDownPreIdx(Index<K, V> current, Node<K, V> expectedNode){
        Index<K, V> currentIdx = current;
        //右邊索引節點
        Index<K, V> rightIdx;
        //右邊數據節點
        Node<K, V> rightNode;

        //不斷向右和向下搜索指定層級的前置索引節點
        while (currentIdx != null){
            //1. 將索引向右移動
            while (true){
                rightIdx = currentIdx.right;
                if(rightIdx == null){
                    break;
                }

                rightNode = rightIdx.node;

                //若是右邊索引節點還在目標節點的左邊,則將索引向右移動
                if(rightNode.compareKeyTo(expectedNode) < 0){
                    currentIdx = currentIdx.right;
                }else if(rightNode.compareKeyTo(expectedNode) == 0){
                    return currentIdx;
                }else{
                    break;
                }
            }

            //2. 將索引向下移動一步
            currentIdx = currentIdx.down;
        }

        return null;
    }

    /** * 查找底層的指定層級的前置索引節點 * @param current 當前索引位置 * @param currentLevel 當前層級 * @param expectedLevel 待查找的層級 * @param expectedNode 目標節點 * @return cn.zifangsky.skiplist.SkipListMap.Index<K, V> */
    private Index<K, V> findDownPreIdx(Index<K, V> current, int currentLevel, int expectedLevel, Node<K, V> expectedNode){
        Index<K, V> currentIdx = current;
        //右邊索引節點
        Index<K, V> rightIdx;
        //右邊數據節點
        Node<K, V> rightNode;

        //不斷向右和向下搜索指定層級的前置索引節點
        while (currentLevel >= 1){
            //1. 將索引向右移動
            while (true){
                rightIdx = currentIdx.right;
                if(rightIdx == null){
                    break;
                }

                rightNode = rightIdx.node;

                //若是右邊索引節點還在目標節點的左邊,則將索引向右移動
                if(rightNode.compareKeyTo(expectedNode) < 0){
                    currentIdx = currentIdx.right;
                }else{
                    break;
                }
            }

            //2. 將索引向下移動一步
            if(currentLevel > expectedLevel){
                currentLevel = (currentLevel -1);
                currentIdx = currentIdx.down;
            }else{
                break;
            }
        }

        return currentIdx;
    }

    /** * 查找指定KEY的前置 * @param key KEY * @return cn.zifangsky.skiplist.SkipListMap.Node<K,V> */
    private Node<K, V> findPreNode(K key){
        Index<K, V> currentIdx = head;
        //下邊索引節點
        Index<K, V> downIdx;
        //右邊索引節點
        Index<K, V> rightIdx;
        //右邊數據節點
        Node<K, V> rightNode;

        //不斷向右和向下搜索指定KEY的前置數據節點
        while (true){
            //1. 將索引向右移動
            while (true){
                rightIdx = currentIdx.right;
                if(rightIdx == null){
                    break;
                }

                rightNode = rightIdx.node;

                //若是右邊索引節點還在目標節點的左邊,則將索引向右移動
                if(rightNode.compareKeyTo(key) < 0){
                    currentIdx = currentIdx.right;
                }else{
                    break;
                }
            }

            downIdx = currentIdx.down;

            //2. 若是下邊索引節點不爲空,則將索引向下移動一步
            if(downIdx != null){
                currentIdx = downIdx;
            }else{
                break;
            }
        }

        //3. 在數據鏈表上繼續向右移動
        Node<K, V> idxNode = currentIdx.node;
        while (true){
            rightNode = idxNode.next;

            //若是右邊數據節點還在目標節點的左邊,則將索引向右移動
            if(rightNode == null || rightNode.compareKeyTo(key) >= 0){
                break;
            }else{
                idxNode = idxNode.next;
            }
        }

        return idxNode;
    }

}
複製代碼

測試用例:

@Test
    public void test(){
        SkipListMap<String,Integer > skipListMap = new SkipListMap<>();
        //1.1 插入
        skipListMap.put("A", 1);
        skipListMap.put("B", 2);
        skipListMap.put("J", 10);
        skipListMap.put("D", 4);
        skipListMap.put("C", 3);
        skipListMap.put("E", 5);
        skipListMap.put("I", 9);
        skipListMap.put("F", 6);
        skipListMap.put("H", 8);
        skipListMap.put("G", 7);

        //1.2 遍歷
        skipListMap.forEach();
        System.out.println("--------------------------------------------------");

        //2.1 查找
        System.out.println("KEY=D,VALUE=" + skipListMap.get("D"));
        System.out.println("KEY=H,VALUE=" + skipListMap.get("H"));
        //2.2 遍歷
        skipListMap.forEach();
        System.out.println("--------------------------------------------------");

        //3. 刪除
        skipListMap.remove("B");
        skipListMap.remove("C");
        skipListMap.remove("G");
        //3.2 遍歷
        skipListMap.forEach();
        System.out.println("--------------------------------------------------");
    }
複製代碼

測試用例輸出以下:

HeadIdx[level=3], Idx[null], Idx[null], Idx[null], Idx[null], Idx[key=E], Idx[null], Idx[key=G], Idx[null], Idx[null], Idx[null]
HeadIdx[level=2], Idx[key=A], Idx[null], Idx[key=C], Idx[null], Idx[key=E], Idx[null], Idx[key=G], Idx[null], Idx[null], Idx[null]
HeadIdx[level=1], Idx[key=A], Idx[null], Idx[key=C], Idx[null], Idx[key=E], Idx[null], Idx[key=G], Idx[null], Idx[null], Idx[null]
HeadNode[BASE_HEADER], Node[key=A, value=1], Node[key=B, value=2], Node[key=C, value=3], Node[key=D, value=4], Node[key=E, value=5], Node[key=F, value=6], Node[key=G, value=7], Node[key=H, value=8], Node[key=I, value=9], Node[key=J, value=10]
--------------------------------------------------
KEY=D,VALUE=4
KEY=H,VALUE=8
HeadIdx[level=3], Idx[null], Idx[null], Idx[null], Idx[null], Idx[key=E], Idx[null], Idx[key=G], Idx[null], Idx[null], Idx[null]
HeadIdx[level=2], Idx[key=A], Idx[null], Idx[key=C], Idx[null], Idx[key=E], Idx[null], Idx[key=G], Idx[null], Idx[null], Idx[null]
HeadIdx[level=1], Idx[key=A], Idx[null], Idx[key=C], Idx[null], Idx[key=E], Idx[null], Idx[key=G], Idx[null], Idx[null], Idx[null]
HeadNode[BASE_HEADER], Node[key=A, value=1], Node[key=B, value=2], Node[key=C, value=3], Node[key=D, value=4], Node[key=E, value=5], Node[key=F, value=6], Node[key=G, value=7], Node[key=H, value=8], Node[key=I, value=9], Node[key=J, value=10]
--------------------------------------------------
HeadIdx[level=3], Idx[null], Idx[null], Idx[key=E], Idx[null], Idx[null], Idx[null], Idx[null]
HeadIdx[level=2], Idx[key=A], Idx[null], Idx[key=E], Idx[null], Idx[null], Idx[null], Idx[null]
HeadIdx[level=1], Idx[key=A], Idx[null], Idx[key=E], Idx[null], Idx[null], Idx[null], Idx[null]
HeadNode[BASE_HEADER], Node[key=A, value=1], Node[key=D, value=4], Node[key=E, value=5], Node[key=F, value=6], Node[key=H, value=8], Node[key=I, value=9], Node[key=J, value=10]
--------------------------------------------------
複製代碼

注:跳躍表的輸出結果是不肯定的,我這裏只是展現了其中一種輸出狀況,僅供參考。

最後,本篇文章到此結束,若是對跳躍表還有什麼不理解的地方,歡迎在下方留言,或者給我發郵件私下溝通。謝謝你們的觀看!

PS:一直不想寫數據結構相關的博客,主要是畫圖太麻煩了,2333。

相關文章
相關標籤/搜索