鏈表實(shí)戰(zhàn)(帶圖分析)

1.1 認(rèn)識(shí)鏈表

1.1.1 簡(jiǎn)介

鏈表這類數(shù)據(jù)結(jié)構(gòu),有點(diǎn)像生活中的火車厢蒜,一節(jié)車廂連著下一節(jié)車廂,在火車?yán)锩媾胫玻挥械搅?號(hào)車廂你才能進(jìn)入5號(hào)車廂斑鸦,一般情況下,不可能直接在3號(hào)車廂繞過(guò)4號(hào)車廂進(jìn)入5號(hào)車廂草雕。不過(guò)更準(zhǔn)確來(lái)說(shuō)巷屿,火車是雙向鏈表,也就是說(shuō)在4號(hào)車廂也可以反向進(jìn)入3號(hào)車廂墩虹。

下面我們來(lái)畫(huà)個(gè)圖看看鏈表這類數(shù)據(jù)結(jié)構(gòu)到底長(zhǎng)啥樣子嘱巾。

1-1-1
1.1.2 特性

每個(gè)鏈表中的節(jié)點(diǎn)都包含一個(gè)值,和指向下一個(gè)節(jié)點(diǎn)的指針诫钓。由此可以定義出節(jié)點(diǎn)的結(jié)構(gòu):

    private class Node {
        public E e;
        public Node next;

        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        public Node(E e) {
            this(e, null);
        }

        @Override
        public String toString() {
            return e.toString();
        }
    }

如果要?jiǎng)?chuàng)建一個(gè)鏈表浓冒,如上圖1-1-1,我們只要知道一個(gè)起始的node節(jié)點(diǎn)即可尖坤。所以每個(gè)鏈表數(shù)據(jù)結(jié)構(gòu)中稳懒,包含的就是一個(gè)起始node頭節(jié)點(diǎn)。

1.2 鏈表插入元素

在一個(gè)如下的鏈表中慢味,index為2的地方场梆,插入節(jié)點(diǎn)15。步驟如下:
第一步纯路,先找到index為1的node節(jié)點(diǎn)23或油;
第二步,然后把節(jié)點(diǎn)15的next指針指向節(jié)點(diǎn)10驰唬;

image.png

第三步顶岸,把節(jié)點(diǎn)23的next指針指向15;

image.png

注意點(diǎn):這里需要先把15節(jié)點(diǎn)的next指向10叫编,然后再把23節(jié)點(diǎn)的next指向15辖佣,順序不能打亂,要不然就找不到23節(jié)點(diǎn)后面的鏈表啦搓逾。

其實(shí)鏈表這類數(shù)據(jù)結(jié)構(gòu)卷谈,一般情況下,比較適應(yīng)于頭尾節(jié)點(diǎn)的操作霞篡,索引index的概念其實(shí)是不存在的世蔗,只是例子中為了方便表示我們插入的位置端逼,才引入了一個(gè)index的概念。

1.2.1 虛擬頭節(jié)點(diǎn)

聰明的你可能發(fā)現(xiàn)了污淋,我們?cè)谔砑釉貢r(shí)顶滩,都是先找到待添加位置的前一個(gè)位置,可是如果待添加位置的index為0呢寸爆?我們就得單獨(dú)開(kāi)設(shè)邏輯來(lái)進(jìn)行判斷礁鲁。代碼如下:

    private Node head;
    private int size;

    public void add(E e, int index) {
        if (index == 0) {
            addFirst(e);
            return;
        }

        Node preNode = head;

        for (int i = 1; i < index; i++) {
            preNode = preNode.next;
        }

        // 這三句話可以整理成一句話
        // preNode.next = new Node(e, preNode.next);
        Node node = new Node(e);
        node.next = preNode.next;
        preNode.next = node;

        size++;
    }

    public void addFirst(E e) {
        head = new Node(e, head);
        size++;
    }

這時(shí),如果我們添加一個(gè)虛擬的頭節(jié)點(diǎn)而昨,就能統(tǒng)一所有的操作救氯,使得邏輯看起來(lái)更加順暢找田。

    private Node dummyHead; // 虛擬頭節(jié)點(diǎn)
    private int size;

    public LinkedList() {
        dummyHead = new Node(null, null);
        size = 0;
    }

    public void add(int index, E e) {

        if (index < 0 || index > size) {
            throw new IllegalArgumentException("index is illegal");
        }

        // 虛擬頭節(jié)點(diǎn)賦值給最開(kāi)始的前一個(gè)節(jié)點(diǎn)
        Node preNode = dummyHead;

        // 找尋index前一個(gè)節(jié)點(diǎn)的索引
        for (int i = 0; i < index; i++) {
            preNode = preNode.next;
        }

        preNode.next = new Node(e, preNode.next);
        // 這三句話可以整理成一句話
//        Node node = new Node(e);
//        node.next = preNode.next;
//        preNode.next = node;

        size++;
    }

1.3 鏈表的遍歷

鏈表的遍歷操作意義很大歌憨,查詢某個(gè)元素,修改某個(gè)元素等操作都會(huì)涉及到鏈表的遍歷墩衙。以下就以鏈表是否包含一個(gè)元素來(lái)展示一下查詢操作务嫡。

    public boolean contains(E e) {
        Node cur = dummyHead.next;
        while (cur != null) {
            if (cur.e.equals(e)) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

1.4 鏈表刪除元素

第一步,找到待刪除元素前一個(gè)元素漆改,如下圖的23心铃;
第二步,把待刪除元素前一個(gè)元素(23)的指針指向待刪除元素(10)的后一個(gè)元素(35)挫剑。
第三步去扣,把刪除元素返回,刪除元素的下一個(gè)元素指針置空樊破,脫離鏈表愉棱。

image.png

實(shí)現(xiàn)代碼如下:

    public E remove(int index) {

        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("index is illegal");
        }

        Node preNode = dummyHead;

        for (int i = 0; i < index; i++) {
            preNode = preNode.next;
        }

        Node delNode = preNode.next;
        preNode.next = delNode.next;
        delNode.next = null;
        size--;

        return delNode.e;
    }

1.5 鏈表實(shí)現(xiàn)棧

鏈表在插入與刪除元素時(shí),如果只是對(duì)頭節(jié)點(diǎn)進(jìn)行操作哲戚,那么時(shí)間復(fù)雜度都是O(1)級(jí)別的奔滑。因?yàn)椴淮嬖趯ぶ愤^(guò)程。所以用它來(lái)實(shí)現(xiàn)棧這種數(shù)據(jù)結(jié)構(gòu)最合適不過(guò)了顺少。

1.6 一些拓展

雙向鏈表朋其,每個(gè)node節(jié)點(diǎn)都有一個(gè)前指針和后指針,一般這種情況下脆炎,還會(huì)增加一個(gè)虛擬的尾節(jié)點(diǎn)用做尾指針梅猿,增加了尋址的速度。

image.png

循環(huán)雙向鏈表秒裕,可以少維護(hù)一個(gè)尾指針粒没,向頭部添加元素就是在虛擬頭節(jié)點(diǎn)前增加元素,向尾部添加元素就是在虛擬頭節(jié)點(diǎn)后插入元素簇爆。

image.png

1.7 leetcode上練習(xí)

leetcode中有許多題目癞松,以下對(duì)一些常見(jiàn)的操作進(jìn)行實(shí)現(xiàn)爽撒。看看具體實(shí)現(xiàn)的思路是什么响蓉。

1.7.1 鏈表反轉(zhuǎn)

最開(kāi)始硕勿,想出來(lái)的是以下這一種方法:在遍歷鏈表的過(guò)程中,不斷new出新的node節(jié)點(diǎn)枫甲。然后把后遍歷的元素添加到鏈表頭位置源武。代碼如下:

    public ListNode reverseList(ListNode head) {
        if (head == null) {
            return null;
        }

        ListNode result = new ListNode(head.val);
        ListNode curr = head;

        while (curr.next != null) {
            ListNode next = new ListNode(curr.next.val);
            next.next = result;
            result = next;

            curr = curr.next;
        }

        return result;
    }

如上代碼具體的圖文解析如下,假如傳進(jìn)來(lái)的鏈表為3想幻,5粱栖,7,8:

image.png

這種方式雖然可以完成反轉(zhuǎn)脏毯,但在反轉(zhuǎn)的過(guò)程中會(huì)不斷的new出新的節(jié)點(diǎn)闹究,有點(diǎn)浪費(fèi)空間。于是乎就在想食店,是否可以就利用原來(lái)的節(jié)點(diǎn)制造出新的鏈表渣淤,但是指針在這種情況下就得先走一步,要不然后續(xù)節(jié)點(diǎn)就會(huì)找不到吉嫩。于是就產(chǎn)生了以下代碼:

    public ListNode reverseList3(ListNode head) {
        if (head == null) {
            return null;
        }

        ListNode result = new ListNode(head.val);
        ListNode cur = head.next;

        while (cur != null) {
            // 記錄住每次循環(huán)時(shí)當(dāng)前的指針
            ListNode current = cur;
            // 指針先行
            cur = cur.next;

            current.next = result;
            result = current;
        }

        return result;
    }

最后還有一種遞歸的方式來(lái)反轉(zhuǎn)鏈表价认,關(guān)于遞歸,以后有機(jī)會(huì)單獨(dú)寫(xiě)一篇文章來(lái)進(jìn)行總結(jié)自娩。利用遞歸來(lái)解決問(wèn)題用踩,思路是把當(dāng)前問(wèn)題拆分成更小的可重復(fù)的問(wèn)題,最終忙迁,當(dāng)問(wèn)題規(guī)模小到一定程度時(shí)脐彩,可以自然求得答案(如下面當(dāng)鏈表的下一個(gè)節(jié)點(diǎn)next為空時(shí),反轉(zhuǎn)鏈表就是本身)动漾。

    private ListNode reverse(ListNode head){
        // 遞歸到底的退出條件
        if (head.next == null) {
            return head;
        }

        // 不能用newHead的next指向當(dāng)前節(jié)點(diǎn)丁屎,因?yàn)閚ewHead始終沒(méi)有動(dòng)
        ListNode newHead = reverse(head.next);
        head.next.next = head;//鏈表循環(huán)
        head.next = null;
        return newHead;
    }

以3,7旱眯,8為例晨川,當(dāng)循環(huán)到底后,return head;返回的就是節(jié)點(diǎn)8删豺。此時(shí)進(jìn)入上次遞歸函數(shù)體內(nèi)共虑,鏈表的示意圖如下:

image.png
image.png

經(jīng)過(guò)這句代碼

head.next.next = head;

將變成如下圖顯示,節(jié)點(diǎn)8指向了節(jié)點(diǎn)7呀页,構(gòu)成了一個(gè)循環(huán)列表了妈拌。

image.png

然后在經(jīng)過(guò)這句代碼:

head.next = null;

就變成如下這個(gè)樣子了:


image.png

到此,完成了一次遞歸操作,我們來(lái)看看這個(gè)例子中更小規(guī)模的問(wèn)題是:兩個(gè)節(jié)點(diǎn)進(jìn)行了一次指針指向的調(diào)換尘分,就是反轉(zhuǎn)猜惋。

接下來(lái)的邏輯,就是重復(fù)以上過(guò)程培愁,整個(gè)過(guò)程如下圖:

image.png

所以最終就是:節(jié)點(diǎn)還是那個(gè)節(jié)點(diǎn)著摔,只是由我愛(ài)(指向)你變成了你愛(ài)(指向)我啦啦啦~,還是雙向鏈表好定续,你有我時(shí)我也有你谍咆。咳咳~~,我們言歸正傳私股,哈哈~~摹察。

1.7.2 尋找中間節(jié)點(diǎn)

尋找鏈表中的中間節(jié)點(diǎn),這個(gè)問(wèn)題倡鲸,最開(kāi)始肯定能夠想到的就是遍歷一下鏈表的長(zhǎng)度供嚎,然后,根據(jù)長(zhǎng)度找到中間節(jié)點(diǎn)旦签。

但是如果僅僅是這樣查坪,肯定是會(huì)被鄙視的寸宏,在leetcode上看到了一個(gè)快慢指針的方式找尋中間節(jié)點(diǎn)的方法宁炫,感覺(jué)很是巧妙。

看看我們?nèi)绾蝸?lái)理解快慢指針的:把鏈表看作一條賽道氮凝,運(yùn)動(dòng)員A速度是運(yùn)動(dòng)員B的兩倍羔巢,當(dāng)運(yùn)動(dòng)員A跑完全程,運(yùn)動(dòng)員B就剛剛好跑了一半罩阵。

我們要做的代碼如下竿秆,fast的next指針為空或者fast的next的next指針為空,則退出尋址循環(huán)稿壁。

slow = slow.next
fast = fast.next.next
image.png

完美找到中點(diǎn)幽钢,這種方式真是優(yōu)雅而又巧妙,遍歷次數(shù)減少到了n/2次傅是。

1.8 題外話

鏈表這種數(shù)據(jù)結(jié)構(gòu)是一種動(dòng)態(tài)的數(shù)據(jù)結(jié)構(gòu)匪燕,節(jié)點(diǎn)node就是一個(gè)信息存放點(diǎn),雙向鏈表也就是一個(gè)節(jié)點(diǎn)中除了存放后指針喧笔,還會(huì)存放前指針帽驯,存放信息更多,那么就會(huì)拓展鏈表的功能书闸。但是同樣的就會(huì)增加維護(hù)的成本尼变,額外的內(nèi)存開(kāi)銷。

由此引申到生活浆劲,數(shù)據(jù)結(jié)構(gòu)和人一樣嫌术,有所長(zhǎng)必有所短哀澈。這個(gè)世界是公平的,人和事都沒(méi)有所謂的完美之說(shuō)度气,少追求完美主義日丹,多接受自身的不足點(diǎn),看到自身閃光點(diǎn)蚯嫌,也許才是快樂(lè)生活的源泉哲虾。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市择示,隨后出現(xiàn)的幾起案子束凑,更是在濱河造成了極大的恐慌,老刑警劉巖栅盲,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汪诉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡谈秫,警方通過(guò)查閱死者的電腦和手機(jī)扒寄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)拟烫,“玉大人该编,你說(shuō)我怎么就攤上這事∷妒纾” “怎么了课竣?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)置媳。 經(jīng)常有香客問(wèn)我于樟,道長(zhǎng),這世上最難降的妖魔是什么拇囊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任迂曲,我火速辦了婚禮,結(jié)果婚禮上寥袭,老公的妹妹穿的比我還像新娘路捧。我一直安慰自己,他們只是感情好纠永,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布鬓长。 她就那樣靜靜地躺著,像睡著了一般尝江。 火紅的嫁衣襯著肌膚如雪涉波。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音啤覆,去河邊找鬼苍日。 笑死,一個(gè)胖子當(dāng)著我的面吹牛窗声,可吹牛的內(nèi)容都是我干的相恃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼笨觅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼拦耐!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起见剩,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤杀糯,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后苍苞,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體固翰,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年羹呵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了骂际。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冈欢,死狀恐怖歉铝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涛癌,我是刑警寧澤犯戏,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布送火,位于F島的核電站拳话,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏种吸。R本人自食惡果不足惜弃衍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坚俗。 院中可真熱鬧镜盯,春花似錦、人聲如沸猖败。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)恩闻。三九已至艺糜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背破停。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工翅楼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人真慢。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓毅臊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親黑界。 傳聞我的和親對(duì)象是個(gè)殘疾皇子管嬉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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

  • 搞懂單鏈表常見(jiàn)面試題 Hello 繼上次的 搞懂基本排序算法,這個(gè)一星期朗鸠,我總結(jié)了宠蚂,我所學(xué)習(xí)和思考的單鏈表基礎(chǔ)知識(shí)...
    醒著的碼者閱讀 4,585評(píng)論 1 45
  • 代碼GitHub地址 鏈表概述 數(shù)組和鏈表都是線性存儲(chǔ)結(jié)構(gòu)的基礎(chǔ)實(shí)現(xiàn),棧和隊(duì)列都是線性存儲(chǔ)結(jié)構(gòu)的應(yīng)用 數(shù)組優(yōu)缺點(diǎn) ...
    HikariCP閱讀 1,385評(píng)論 0 0
  • 本文是對(duì) Swift Algorithm Club 翻譯的一篇文章童社。Swift Algorithm Club是 r...
    Andy_Ron閱讀 1,315評(píng)論 1 5
  • //leetcode中還有花樣鏈表題求厕,這里幾個(gè)例子,冰山一角 求單鏈表中結(jié)點(diǎn)的個(gè)數(shù)----時(shí)間復(fù)雜度O(n)這是最...
    暗黑破壞球嘿哈閱讀 1,518評(píng)論 0 6
  • 單鏈表 單鏈表問(wèn)題與思路 找出單鏈表的倒數(shù)第K個(gè)元素(僅允許遍歷一遍鏈表)使用指針追趕的方法扰楼。定義兩個(gè)指針fast...
    wxkkkkk閱讀 600評(píng)論 0 0