亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

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

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

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

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

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

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

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

/**
* 初始化
*/
public void init() {
    //帶頭節(jié)點(diǎn)的鏈表,便于操作
    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é)點(diǎn)保存(最底層)
    indexList.add(head);

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

從跳表中查找元素:從最頂層索引鏈表開始查找,找到第一個(gè)大于當(dāng)前節(jié)點(diǎn)的元素,則需要查找的元素在當(dāng)前節(jié)點(diǎn)與之前節(jié)點(diǎn)之間,則從當(dāng)前節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)prev往下nodevalue繼續(xù)進(jìn)行查找,直到當(dāng)前節(jié)點(diǎn)值與查找值相等,則直接返回當(dāng)前節(jié)點(diǎn),返回的節(jié)點(diǎn)可能是索引節(jié)點(diǎn),也可能是原始數(shù)據(jù)節(jié)點(diǎn),如果需要找到原始數(shù)據(jù)節(jié)點(diǎn),則通過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.找到第一個(gè)大于當(dāng)前元素的數(shù),找前一個(gè)
    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;
            }
            //找到了第一個(gè)大于num的索引node
            //到下一層去繼續(xù)找
            node = last.nodeValue;
            last = null;
            continue;
        }
        last = node;
        node = null != node.next ? node.next : node.nodeValue;
    }
    return null;
}

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

/**
 * 構(gòu)建索引時(shí):自底向上逐層構(gòu)建,如果索引需要?jiǎng)h除(當(dāng)兩個(gè)索引之間沒有任何數(shù)據(jù)時(shí)候,刪除)
 * @param num
 * @return
 */
public boolean remove(int num) {
    Node node = this.findNum(num);
    if (null == node) {
        //不需要移除
        return false;
    }
    if (null == node.nodeValue) {
        //數(shù)據(jù)鏈表,可以直接移除
        //是否最后一個(gè)節(jié)點(diǎn)
        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é)點(diǎn):新增節(jié)點(diǎn)時(shí)候,如果不對(duì)索引進(jìn)行調(diào)整,極端情況下,每次新增的節(jié)點(diǎn)都在之前第一層兩個(gè)節(jié)點(diǎn)之間,當(dāng)這之間的鏈表越變?cè)介L,時(shí)間復(fù)雜度直接退化為O(n),所以需要同時(shí)新增索引,維持跳表的高效性。但是我們?nèi)绾涡略?,有一個(gè)方法就是,在新增節(jié)點(diǎn)時(shí),隨機(jī)選擇k,即第k級(jí)索引,從1~k新增索引。

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

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

        node = node.next;
    }

}

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

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

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

相關(guān)文章

  • 關(guān)于JAVA中this的使用方法小結(jié)

    關(guān)于JAVA中this的使用方法小結(jié)

    現(xiàn)在讓大家看一個(gè)小例子,給你分享一下JAVA中“this”的用法,有需要的朋友可以參考一下
    2013-10-10
  • Spingmvc中的HandlerMapping剖析

    Spingmvc中的HandlerMapping剖析

    這篇文章主要介紹了Spingmvc中的HandlerMapping剖析,Spingmvc中的HandlerMapping負(fù)責(zé)解析請(qǐng)求URL,對(duì)應(yīng)到Handler進(jìn)行處理,這里的Handler一般為Controller里的一個(gè)方法method,也可以為servlet或者Controller等,需要的朋友可以參考下
    2023-09-09
  • 解決mybatis where-if中if不能識(shí)別大寫AND,OR的問題

    解決mybatis where-if中if不能識(shí)別大寫AND,OR的問題

    這篇文章主要介紹了解決mybatis where-if中if不能識(shí)別大寫AND,OR的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • Java中l(wèi)ist根據(jù)id獲取對(duì)象的幾種方式

    Java中l(wèi)ist根據(jù)id獲取對(duì)象的幾種方式

    這篇文章主要給大家介紹了關(guān)于Java中l(wèi)ist根據(jù)id獲取對(duì)象的幾種方式,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-07-07
  • SpringBoot在生產(chǎn)快速禁用Swagger2的方法步驟

    SpringBoot在生產(chǎn)快速禁用Swagger2的方法步驟

    這篇文章主要介紹了SpringBoot在生產(chǎn)快速禁用Swagger2的方法步驟,使用注解關(guān)閉Swagger2,避免接口重復(fù)暴露,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2018-12-12
  • Spring深入刨析聲明式事務(wù)

    Spring深入刨析聲明式事務(wù)

    在spring注解中,使用聲明式事務(wù),需要用到兩個(gè)核心的注解:@Transactional注解和@EnableTransactionManagement注解。將@Transactional注解加在方法上,@EnableTransactionManagement注解加在配置類上
    2022-12-12
  • Java設(shè)計(jì)模式中的門面模式詳解

    Java設(shè)計(jì)模式中的門面模式詳解

    門面模式又叫外觀模式(Facade Pattern),主要用于隱藏系統(tǒng)的復(fù)雜性,并向客戶端提供了一個(gè)客戶端可以訪問系統(tǒng)的接口,本文通過實(shí)例代碼給大家介紹下java門面模式的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2022-09-09
  • java實(shí)現(xiàn)畫圖板功能

    java實(shí)現(xiàn)畫圖板功能

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)畫圖板功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-09-09
  • MyBatis CodeHelperPro激活方法詳細(xì)教程

    MyBatis CodeHelperPro激活方法詳細(xì)教程

    MyBatisCodeHelper-Pro是IDEA下的一個(gè)插件,功能類似mybatis plugin,今天小編給大家分享MyBatis CodeHelperPro激活方法,需要的朋友跟隨小編一起看看吧
    2021-07-07
  • Mybatis攔截器實(shí)現(xiàn)分頁

    Mybatis攔截器實(shí)現(xiàn)分頁

    本文介紹使用Mybatis攔截器,實(shí)現(xiàn)分頁;并且在dao層,直接返回自定義的分頁對(duì)象。具有很好的參考價(jià)值,下面跟著小編一起來看下吧
    2017-01-01

最新評(píng)論