大話數(shù)據(jù)結(jié)構(gòu)之鏈表(二)

上一篇《鏈表概念篇》中, 主要給小伙伴們講述了什么是鏈表? 為什么鏈表是線性結(jié)構(gòu)驮履? 鏈表的操作是什么? 鏈表操作的過(guò)程與原理是什么廉嚼?相信認(rèn)真讀過(guò)的小伙伴們已經(jīng)明白的掌握了鏈表的相關(guān)概念玫镐。 那么下面來(lái)回顧一下

1. 什么是鏈表?

鏈表是一種鏈?zhǔn)降臄?shù)據(jù)結(jié)構(gòu)前鹅, 兩個(gè)節(jié)點(diǎn)之間使用鏈?zhǔn)竭B接摘悴, 比如 單鏈表的A的next是B, B的next是C, C的next是D舰绘,可以標(biāo)識(shí)為A -> B -> C -> D蹂喻。 雙鏈表在單鏈表的基礎(chǔ)上添加了前指向pre, 如A的next是B,則必然B的pre是A捂寿, 理解為A是B的前驅(qū)口四, B是A的后繼。

2. 鏈表的操作流程

一般常用的操作也就是我們熟悉的增刪改查秦陋。其中查和改很簡(jiǎn)單蔓彩, 與數(shù)組不同的是,我們是通過(guò)頭節(jié)點(diǎn)驳概,不斷的遍歷相鄰的下個(gè)節(jié)點(diǎn)赤嚼,直到找到你想要的節(jié)點(diǎn)元素;既然找到了顺又,那么修改節(jié)點(diǎn)的值就簡(jiǎn)單的不能再簡(jiǎn)單了更卒。鏈表中稍微復(fù)雜些的操作就是增添與刪除節(jié)點(diǎn)。 上一篇中我也給小伙伴們列了相應(yīng)的步驟:
1) 首先找到相關(guān)的節(jié)點(diǎn)稚照,即目標(biāo)節(jié)點(diǎn)的前驅(qū)與后記
2) 斷開(kāi)原有的鏈接關(guān)系
3) 建立新的鏈接關(guān)系

如果還對(duì)鏈表的概念不清楚蹂空,建議回頭查看上一篇《鏈表 概念篇》


鏈表在數(shù)據(jù)結(jié)構(gòu)中算是比較重要的結(jié)構(gòu)之一俯萌,希望大家真的把與鏈表相關(guān)的知識(shí)弄明白。 那么接下來(lái)上枕,我們講解一下LinkedList的源碼咐熙。其實(shí)明白了常用操作的原理, 還是很好理解的辨萍。

LinkedList源碼解讀

首先棋恼,我們來(lái)看一下常用的操作

image.png

從上述可以看出,提供的常用接口分瘦,無(wú)非就是CRUD或者CRUD的變種蘸泻; 那么如果你掌握了CRUD琉苇,至于他們的變種就可以一通百通了嘲玫。如addFirst , addLast, add, addAll等都屬于增加吧。

下面主要來(lái)說(shuō)一下鏈表的增刪改查并扇。

1. 先來(lái)看一下最簡(jiǎn)單的 查
 /**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index {@code i} such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     *
     * @param o element to search for
     * @return the index of the first occurrence of the specified element in
     *         this list, or -1 if this list does not contain the element
     */
    public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }

代碼很少去团,我們不妨來(lái)看一下最核心的地方

for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }

for循環(huán),但不是我們常用的for循環(huán)穷蛹。 默認(rèn)初始化是x = first, 從第一個(gè)節(jié)點(diǎn)開(kāi)始土陪; x =x.next, 不斷查找下一個(gè)節(jié)點(diǎn); 判斷節(jié)點(diǎn)的值是否是我們傳入的值肴熏,如果是鬼雀,那么恭喜你,找到了蛙吏!同樣的步驟源哩,我們可以返回在整個(gè)鏈表的位置index, 也可以返回找到的節(jié)點(diǎn), 當(dāng)前還可以返回目標(biāo)節(jié)點(diǎn)的前驅(qū)結(jié)點(diǎn)和后繼結(jié)點(diǎn)鸦做。 這也就滿足了我們的查找與增刪操作時(shí)需要的目標(biāo)節(jié)點(diǎn)的前驅(qū)和后繼了吧励烦。

當(dāng)然,如果你經(jīng)常使用數(shù)據(jù)庫(kù)Cursor遍歷的話泼诱,你會(huì)發(fā)現(xiàn)真的很像坛掠。

2. 順帶著,我們看一下修改的操作

關(guān)于修改治筒,我真的不想說(shuō)了屉栓。當(dāng)我們成功找到了想要修改的節(jié)點(diǎn)后,你還不會(huì)修改節(jié)點(diǎn)的值么耸袜?

3. 鏈表的核心操作之插入一個(gè)元素

鏈表提供的插入Api主要有兩個(gè)友多, 那就是頭插法和尾插法。 當(dāng)然還有一些衍生的操作句灌,不過(guò)掌握了這兩種主要的核心方法夷陋,其他一搭眼欠拾,你就明白為什么了。

  /**
     * Links e as first element.
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

頭插法骗绕,即將節(jié)點(diǎn)插入到頭部藐窄。我們來(lái)想一下這個(gè)過(guò)程,現(xiàn)在已知一個(gè)鏈表酬土,如下
A -> B -> C -> D
可以看出荆忍,A是first,也即頭節(jié)點(diǎn);如果我們想在頭部插入一個(gè)節(jié)點(diǎn)E撤缴, 那么按照我們總結(jié)的步驟
E->A->B->C->D

  1. 目標(biāo)節(jié)點(diǎn)是頭節(jié)點(diǎn)刹枉,因此沒(méi)有前驅(qū),有一個(gè)后繼A屈呕,是當(dāng)前的first
  2. 插入頭部或尾部微宝,不涉及斷鏈問(wèn)題
  3. 建立新鏈接, E的next指向A虎眨, 如果是雙鏈表的話蟋软,當(dāng)然A的pre也指向E

再看代碼

  1. 先找到了相關(guān)節(jié)點(diǎn)first, 保存起來(lái)
 final Node<E> f = first;
  1. 根據(jù)數(shù)據(jù)嗽桩,生成新的目標(biāo)節(jié)點(diǎn);新節(jié)點(diǎn)的next是f也就是first
  2. 插入后岳守,新節(jié)點(diǎn)就是first了
 first = newNode;
  1. 如果頭節(jié)點(diǎn)是空,那么插入后碌冶,就只有一個(gè)節(jié)點(diǎn)newNode了湿痢,因此既是頭,也是尾扑庞。如果頭節(jié)點(diǎn)不是空譬重,這里A的前驅(qū)指向newNode,前面在newNode創(chuàng)建時(shí)嫩挤,已經(jīng)指定了后繼是f害幅。 這時(shí)候,一條新的鏈就創(chuàng)建起來(lái)了岂昭。

從以上來(lái)看以现, LinkedList是個(gè)雙鏈表,因?yàn)榇嬖趐re指針约啊。雙鏈表比單鏈表的高校在于: 單鏈表是單鏈邑遏,只能從頭到尾找;雙鏈表是雙鏈表恰矩,既可以從頭向后找记盒,也可以從尾向前找。

再來(lái)看尾插法外傅, 明白了頭插法纪吮,那么尾插法就手到擒來(lái)了俩檬,你只要畫一個(gè)圖,就可以照著寫出來(lái)碾盟。還是那個(gè)例子棚辽,ABCD插入E,這次是從尾部冰肴,那么

A -> B -> C->D
A->B->C->D->E

根據(jù)頭插法的分析屈藐,涉及的前驅(qū)只有D, 沒(méi)有后繼熙尉。

 /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

與頭插法的實(shí)現(xiàn)幾乎是相同的联逻,這里不做解釋了。

頭插法和尾插法相對(duì)比較簡(jiǎn)單检痰, 因?yàn)橹簧婕耙粋€(gè)元素前驅(qū)或后繼包归, 那么來(lái)看一下中間插入是怎么做的? 相比于以上兩種攀细,只是多了一層前驅(qū)或后繼的斷開(kāi)與重鏈箫踩。

/**
     * Inserts element e before non-null Node succ.
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

上述代碼是在succ節(jié)點(diǎn)前面插入一個(gè)元素為e的新節(jié)點(diǎn),linkAfter實(shí)現(xiàn)與之相似谭贪,這里重點(diǎn)說(shuō)一下這個(gè), 你可以試著自己寫一下在后面插入的實(shí)現(xiàn)

當(dāng)前的狀況: succ.pre -> succ -> succ.next
插入后: succ.pre -> E -> succ -> succ.next

我們來(lái)分析一下:

  1. 還是找受影響的目標(biāo)節(jié)點(diǎn)E的前驅(qū)與后繼锦担,分別是succ.pre以及succ
  2. 看圖俭识,插入E,我們需要斷開(kāi)succ.pre與succ的鏈洞渔;因?yàn)椴迦牒筇酌模瑂ucc的前驅(qū)是E了,而不是以前的succ.pre
  3. 重新建立鏈磁椒, succ.pre與E之間堤瘤, E與succ之間

根據(jù)以上分析,我們來(lái)看源碼

1.找到并保存影響節(jié)點(diǎn) succ.pre
  final Node<E> pred = succ.prev;
2. 斷開(kāi)并重建succ與E的鏈
  final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;

這里你就奇怪了浆熔,為啥斷開(kāi)和建鏈?zhǔn)窃谝黄鸬哪兀?我們斷開(kāi)是通過(guò)設(shè)置next或pre為null來(lái)實(shí)現(xiàn)的本辐,便于理解; 但是之后會(huì)在next或pre上添加新的關(guān)系医增, 這里把兩個(gè)過(guò)程合并了慎皱。比如

我們應(yīng)該先斷開(kāi)pre與succ的鏈,那么我們應(yīng)該這么寫

pred.next = null; // 斷開(kāi)前驅(qū)的后繼關(guān)系
succ.pre = null; // 斷開(kāi)succ的前驅(qū)關(guān)系

然后建立新的鏈

succ.pre = E; // E就是newNode
E.next = succ;

E的鏈的建立是在構(gòu)造中叶骨,new Node(前驅(qū)茫多, 數(shù)據(jù), 后繼)忽刽。別說(shuō)沒(méi)有鏈到succ哈天揖!
接下來(lái)我們把以下代碼合并為一個(gè)

succ.pre = null;
succ.pre = E;

我們直接合并成

succ.pre = E;

到這里夺欲,你明白了嗎?

好了今膊,插入的解析到此結(jié)束了洁闰;接下來(lái)解析刪除的代碼。其實(shí)明白了插入万细,刪除很容易理解扑眉,因?yàn)槎际菙噫満徒ㄦ湹倪^(guò)程

 E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

還是先模擬一下過(guò)程,已知一個(gè)鏈表ABCD, 此時(shí)我想刪除C赖钞, 那么過(guò)程是:
A - B - C - D
A - B - D

  1. 首先找到目標(biāo)C以受影響的前驅(qū)B與后繼D腰素,并保存
  2. 斷開(kāi)C與B, C與D的關(guān)系
  3. 重新建立B與D的關(guān)系

看源碼

pre.next = next;

可能比較繞雪营, pre就是B弓千, next就是D, 此時(shí)是刪除C后献起,讓B的后繼指向D洋访。同時(shí),

next.prev = prev;

D的前驅(qū)指向B谴餐。 鏈的斷開(kāi)與建立就有了姻政。
···
x.pre = null;
x.next = null;
···
這就斷開(kāi)C的所有鏈了,也就是說(shuō)C是一個(gè)獨(dú)立節(jié)點(diǎn)了岂嗓。

本篇的意義汁展,就是在理解理論的前提下,通過(guò)看系統(tǒng)的實(shí)現(xiàn)來(lái)驗(yàn)證理論厌殉,并加深印象食绿,以及知道理論是如何用代碼實(shí)現(xiàn)的。整個(gè)過(guò)程其實(shí)理解了理論后公罕,還是很容易看懂的器紧。

我一直在強(qiáng)調(diào)一個(gè)流程,至于斷開(kāi)與重建鏈的過(guò)程楼眷,并不一定是誰(shuí)先誰(shuí)后的铲汪;只是這樣解釋比較好理解;在代碼中我們也看到了摩桶,斷開(kāi)與重建的過(guò)程是同時(shí)進(jìn)行的桥状,當(dāng)然這要建立在你理解整個(gè)過(guò)程之后,因此建議你自己手寫一遍這個(gè)過(guò)程硝清,才能真正明白它究竟是怎么做的辅斟。

好了,通過(guò)這兩篇文章芦拿,相信你已經(jīng)理解了鏈表這種結(jié)構(gòu)了士飒。如果你覺(jué)得本文對(duì)你有幫助查邢,不妨點(diǎn)個(gè)贊,謝謝酵幕!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末扰藕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子芳撒,更是在濱河造成了極大的恐慌邓深,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笔刹,死亡現(xiàn)場(chǎng)離奇詭異芥备,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)舌菜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門萌壳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人日月,你說(shuō)我怎么就攤上這事袱瓮。” “怎么了爱咬?”我有些...
    開(kāi)封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵尺借,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我台颠,道長(zhǎng)褐望,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任串前,我火速辦了婚禮,結(jié)果婚禮上实蔽,老公的妹妹穿的比我還像新娘荡碾。我一直安慰自己,他們只是感情好局装,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布坛吁。 她就那樣靜靜地躺著,像睡著了一般铐尚。 火紅的嫁衣襯著肌膚如雪拨脉。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天宣增,我揣著相機(jī)與錄音玫膀,去河邊找鬼。 笑死爹脾,一個(gè)胖子當(dāng)著我的面吹牛帖旨,可吹牛的內(nèi)容都是我干的箕昭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼解阅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼落竹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起货抄,我...
    開(kāi)封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤述召,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蟹地,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體积暖,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年锈津,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呀酸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡琼梆,死狀恐怖性誉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情茎杂,我是刑警寧澤错览,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站煌往,受9級(jí)特大地震影響倾哺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜刽脖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一羞海、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧曲管,春花似錦却邓、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至檬某,卻和暖如春撬腾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恢恼。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工民傻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓饰潜,卻偏偏與公主長(zhǎng)得像初坠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子彭雾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容