最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java實現(xiàn)跳躍表的示例詳解

 更新時間:2022年05月13日 12:52:37   作者:Carol淋  
跳表全稱叫做跳躍表,簡稱跳表,是一個隨機化的數(shù)據(jù)結(jié)構(gòu),實質(zhì)就是一種可以進(jìn)行二分查找的有序鏈表。本文將利用Java語言編寫一個跳表,需要的可以參考一下

跳表全稱叫做跳躍表,簡稱跳表,是一個隨機化的數(shù)據(jù)結(jié)構(gòu),實質(zhì)就是一種可以進(jìn)行二分查找的有序鏈表。跳表在原有的有序列表上面增加多級索引,通過索引來實現(xiàn)快速查找。跳表不僅能提高搜索性能,同時也提高插入和刪除的性能,redis中的有序集合set就是用跳表實現(xiàn)的,面試時候也經(jīng)常會問。 

這里我們原始數(shù)據(jù)個數(shù)n=10,以間隔k=2建立索引,則第一層索引10/2=5個,第二層⌈10/2^2⌉=3個,第三層⌈10/2^3⌉=2個,第四層⌈10/2^4⌉=1個。根據(jù)上圖我們來分析一下,跳表的結(jié)構(gòu)是一棵樹(除原始數(shù)據(jù)層外),樹的左指針指向?qū)?yīng)的下一層鏈表的節(jié)點,右指針指向當(dāng)前鏈表的下一個節(jié)點,且樹高為log(n),對于每一層需要比較的次數(shù)最多為k,則時間復(fù)雜度為O(k*log(n)),k為常數(shù)項,所以跳表查詢時間復(fù)雜度為O(log(n))。因為需要額外的空間存儲索引,是典型的以空間換時間,空間復(fù)雜度為O(n)。

接下來我們自己實現(xiàn)一個跳表:

節(jié)點數(shù)據(jù)結(jié)構(gòu)定義:根據(jù)跳表結(jié)構(gòu),節(jié)點首先需要一個value存儲當(dāng)前節(jié)點值,需要一個next指針指向同一層的下一個節(jié)點,需要一個nodeValue指針指向下一層對應(yīng)節(jié)點,但是這里為了插入刪除方便,引入了一個prev指針,指向同一層的上一個節(jié)點。

class Node {
    //當(dāng)前節(jié)點值
    private Integer value;
    //當(dāng)前節(jié)點所屬鏈表下一個節(jié)點
    private Node next;
    //當(dāng)前節(jié)點所屬鏈表上一個節(jié)點
    private Node prev;
    //當(dāng)前節(jié)點指向的另一個索引鏈表/原始值鏈表節(jié)點
    private Node nodeValue;
    Node(Integer value) {
        this.value = value;
    }
}

初始化一個跳表:跳表的建立需要在數(shù)據(jù)有序的基礎(chǔ)上,然后從下往上在下一層的基礎(chǔ)上,間隔k生成當(dāng)前層的節(jié)點,新生成的節(jié)點需要與當(dāng)前層上一個節(jié)點連接起來,并且指向生成它的下一層節(jié)點。

/**
 * 原始數(shù)據(jù)鏈表
 */
private Node head ;
/**
 * 最終的跳表結(jié)構(gòu):保存索引鏈表及原始鏈表
 */
private List<Node> indexList;
/**
 * 跳表層數(shù)
 */
private int level;

/**
* 初始化
*/
public void init() {
    //帶頭節(jié)點的鏈表,便于操作
    head = new Node(-1);
    head.next = head;
    indexList = new ArrayList<>();
    level = 0;
}
/**
 * 初始化跳表
 * @param k 間隔
 * @param nums 原始數(shù)據(jù)(已排序)
 */
public void init(int k, int[] nums) {
    //初始化數(shù)據(jù)鏈表
    Node temp = head;
    for (int num : nums) {
        Node cur = new Node(num);
        cur.prev = temp;
        temp.next = cur;
        temp = temp.next;
    }
    //新節(jié)點保存(最底層)
    indexList.add(head);

    //循環(huán)生成索引結(jié)構(gòu),結(jié)束條件,當(dāng)層僅一個元素
    temp = head.next;
    while (true) {
        //當(dāng)前鏈表第幾個元素
        int i = 0;
        //生成另一條鏈表長度
        int size = 0;
        Node indexNode = new Node(-1);
        indexNode.next = indexNode;
        Node indexNodeTemp = indexNode;
        while (null != temp) {
            //間隔k生成節(jié)點
            if (i % k == 0) {
                Node curNode = new Node(temp.value);
                curNode.nodeValue = temp;
                curNode.prev = indexNodeTemp;
                indexNodeTemp.next = curNode;
                indexNodeTemp = indexNodeTemp.next;
                ++ size;
            }
            ++ i;
            temp = temp.next;
        }
        indexList.add(indexNode);
        temp = indexNode.next;
        //當(dāng)生成的索引鏈表僅1時不需要再繼續(xù)生成
        if (size == 1) {
            break;
        }
    }
    level = indexList.size();
}

從跳表中查找元素:從最頂層索引鏈表開始查找,找到第一個大于當(dāng)前節(jié)點的元素,則需要查找的元素在當(dāng)前節(jié)點與之前節(jié)點之間,則從當(dāng)前節(jié)點的上一個節(jié)點prev往下nodevalue繼續(xù)進(jìn)行查找,直到當(dāng)前節(jié)點值與查找值相等,則直接返回當(dāng)前節(jié)點,返回的節(jié)點可能是索引節(jié)點,也可能是原始數(shù)據(jù)節(jié)點,如果需要找到原始數(shù)據(jù)節(jié)點,則通過nodeValue繼續(xù)往下找。

/**
 * 是否存在num
 * @param num
 * @return
 */
public boolean hasNum(int num) {
    Node result = this.findNum(num);
    return null != result;
}
/**
 * 查找num(返回的可能是索引,也可能是原始數(shù)據(jù),根據(jù)nodeValue可以判斷,也可以找到原始數(shù)據(jù))
 * @param num
 */
public Node findNum(int num) {
    //跳表結(jié)構(gòu)indexList是數(shù)據(jù)-》第一層索引-》第二層索引-》。。。。
    //1.直接匹配到
    //2.找到第一個大于當(dāng)前元素的數(shù),找前一個
    Node node = indexList.get(indexList.size() - 1).next;
    Node last = null;
    while (null != node) {
        if (node.value == num) {
            //已經(jīng)找到元素
            return node;
        }
        if (node.value > num) {
            if (null == last) {
                //比最小值還小
                return null;
            }
            //找到了第一個大于num的索引node
            //到下一層去繼續(xù)找
            node = last.nodeValue;
            last = null;
            continue;
        }
        last = node;
        node = null != node.next ? node.next : node.nodeValue;
    }
    return null;
}

刪除節(jié)點:首先通過上面的查找方法找到目標(biāo)節(jié)點,如果目標(biāo)節(jié)點是索引值,則需要從當(dāng)前索引層,層層往下刪除包括原始數(shù)據(jù)鏈表,如果是原始數(shù)據(jù)值,則直接刪除,暫不調(diào)整。

/**
 * 構(gòu)建索引時:自底向上逐層構(gòu)建,如果索引需要刪除(當(dāng)兩個索引之間沒有任何數(shù)據(jù)時候,刪除)
 * @param num
 * @return
 */
public boolean remove(int num) {
    Node node = this.findNum(num);
    if (null == node) {
        //不需要移除
        return false;
    }
    if (null == node.nodeValue) {
        //數(shù)據(jù)鏈表,可以直接移除
        //是否最后一個節(jié)點
        if (null == node.next) {
            node.prev.next = null;
            return true;
        }
        node.next.prev = node.prev;
        node.prev.next = node.next;
        return true;
    }
    //當(dāng)前在索引上,自上而下刪除索引及數(shù)據(jù)
    while (null != node) {
        Node cur = node.nodeValue;
        if (null == node.next) {
            node.prev.next = null;
        } else {
            node.next.prev = node.prev;
            node.prev.next = node.next;
        }
        node = cur;
    }
    return true;
}

新增節(jié)點:新增節(jié)點時候,如果不對索引進(jìn)行調(diào)整,極端情況下,每次新增的節(jié)點都在之前第一層兩個節(jié)點之間,當(dāng)這之間的鏈表越變越長,時間復(fù)雜度直接退化為O(n),所以需要同時新增索引,維持跳表的高效性。但是我們?nèi)绾涡略?,有一個方法就是,在新增節(jié)點時,隨機選擇k,即第k級索引,從1~k新增索引。

/**
 * 首先需要查找插入位置,如果比最小的還小,直接在前面插入
 * 否則需要從最頂級一直查找到數(shù)據(jù)鏈表,找到插入位置,插入,在查找的過程中,就可以開始插入索引節(jié)點,
 * 從上往下進(jìn)行插入
 * @param num
 */
public void add(int num) {
    int k = this.generatorLevelK();
    //尋找插入點的過程和查找過程基本一致
    //頂級索引鏈表
    Node node = indexList.get(indexList.size() - 1).next;
    int index = 1;
    while (null != node) {
        //找到第一個node.value >= num的元素,在前面插入
        if (node.value >= num) {
            //已經(jīng)找到,前插
            if (index >= k) {
                Node newNode = new Node(num);
                Node temp = node.prev;
                newNode.next = temp.next;
                temp.next.prev = newNode;
                newNode.prev = temp;
                temp.next = newNode;
            }
            //找的時候往后面找的,但是當(dāng)前已經(jīng)先于num了,下一次再往后面找,就出現(xiàn)問題
            if (null == node.prev.prev) {
                //第一個節(jié)點就符合條件
                node = node.nodeValue;
                continue;
            }
            node = node.prev.nodeValue;
            ++ index;
            continue;
        }

        //沒有找到,但是當(dāng)前已經(jīng)是鏈表最后一個元素了
        if (null == node.next) {
            if (index >= k) {
                Node newNode = new Node(num);
                newNode.prev = node;
                node.next = newNode;
            }
            if (null == node.prev.prev) {
                //第一個節(jié)點就符合條件
                node = node.nodeValue;
                continue;
            }
            node = node.prev.nodeValue;
            ++ index;
            continue;
        }

        node = node.next;
    }

}

private int generatorLevelK() {
    Random random = new Random();
    return random.nextInt(level);
}

至此,我們實現(xiàn)了一個跳表的定義,初始化,查找,節(jié)點新增與刪除。

到此這篇關(guān)于Java實現(xiàn)跳躍表的示例詳解的文章就介紹到這了,更多相關(guān)Java跳躍表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringMVC在多線程下請求頭獲取失敗問題的解決方案

    SpringMVC在多線程下請求頭獲取失敗問題的解決方案

    這篇文章主要介紹了我們就對多線程環(huán)境下使用SpringMVC中RequestContextHolder無法獲取請求的問題進(jìn)行了深入的分析,并針對相關(guān)問題給出了相應(yīng)的解決方案,需要的朋友可以參考下
    2024-08-08
  • 深入解析Java中ThreadLocal線程類的作用和用法

    深入解析Java中ThreadLocal線程類的作用和用法

    ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路,ThreadLocal并不是一個Thread,而是Thread的局部變量,本文就來深入解析Java中ThreadLocal線程類的作用和用法.
    2016-05-05
  • SpringBoot集成Redis實現(xiàn)驗證碼的簡單案例

    SpringBoot集成Redis實現(xiàn)驗證碼的簡單案例

    本文主要介紹了SpringBoot集成Redis實現(xiàn)驗證碼的簡單案例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • 如何設(shè)計一個安全的API接口詳解

    如何設(shè)計一個安全的API接口詳解

    在日常開發(fā)中,總會接觸到各種接口,前后端數(shù)據(jù)傳輸接口,第三方業(yè)務(wù)平臺接口,下面這篇文章主要給大家介紹了關(guān)于如何設(shè)計一個安全的API接口的相關(guān)資料,需要的朋友可以參考下
    2021-08-08
  • SpringBoot集成Redis流程詳解

    SpringBoot集成Redis流程詳解

    這篇文章主要介紹了SpringBoot集成Redis流程詳解,導(dǎo)入jar包,編寫配置類,編寫util類,配置yml這四個步驟,有詳細(xì)的代碼示例,,需要的朋友可以參考下
    2023-05-05
  • javassist使用指南

    javassist使用指南

    這篇文章主要介紹了javassist的使用方法,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07
  • 淺談使用java實現(xiàn)阿里云消息隊列簡單封裝

    淺談使用java實現(xiàn)阿里云消息隊列簡單封裝

    這篇文章主要介紹了淺談使用java實現(xiàn)阿里云消息隊列簡單封裝,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • SpringBoot實現(xiàn)websocket服務(wù)端及客戶端的詳細(xì)過程

    SpringBoot實現(xiàn)websocket服務(wù)端及客戶端的詳細(xì)過程

    文章介紹了WebSocket通信過程、服務(wù)端和客戶端的實現(xiàn),以及可能遇到的問題及解決方案,感興趣的朋友一起看看吧
    2024-12-12
  • Spring使用aop切面編程時要給那些類加注解的實例

    Spring使用aop切面編程時要給那些類加注解的實例

    在使用切面編程時,通常需要為以下類或組件添加注解來標(biāo)識它們,以便 Spring 或其他切面框架能夠正確識別和處理它們,這篇文章主要介紹了Spring使用aop切面編程時要給那些類加注解,需要的朋友可以參考下
    2023-11-11
  • Java如何發(fā)起http請求的實現(xiàn)(GET/POST)

    Java如何發(fā)起http請求的實現(xiàn)(GET/POST)

    這篇文章主要介紹了Java如何發(fā)起http請求的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03

最新評論

改则县| 天等县| 外汇| 额敏县| 湘潭县| 大竹县| 鱼台县| 册亨县| 海兴县| 扬州市| 武宣县| 安顺市| 武乡县| 兴仁县| 通山县| 绍兴县| 弥渡县| 峨眉山市| 闽清县| 江达县| 汤阴县| 岱山县| 湖南省| 腾冲县| 苏尼特左旗| 四子王旗| 永安市| 昆山市| 隆尧县| 巍山| 中江县| 济南市| 马公市| 华安县| 固原市| 沾化县| 临夏县| 观塘区| 阿勒泰市| 星子县| 东兰县|