連結串列的概念、實踐和麵試題

?沙漠駱駝發表於2020-04-19

一、連結串列簡介

連結串列是有序的列表,在記憶體中的儲存方式如圖:

  • 連結串列是以節點的方式來儲存,是鏈式儲存;
  • 每個節點包含data域,next指標(指向下一個節點);
  • 連結串列的節點不一定連續儲存,是離散的狀態;
  • 連結串列分為帶頭節點的連結串列和沒有頭結點的連結串列,帶頭結點的邏輯結構示意圖如下:

二、單連結串列的應用例項

2.1 單連結串列的基礎方法的實現

使用帶頭結點的單向連結串列實現,完成對水滸英雄的CRUD。

  • 普通新增方法
    此方法新增英雄時,直接新增到連結串列尾部。
  1. 先建立一個head節點,不記錄具體資訊,作用就是表示單連結串列的頭結點;
  2. 此後每新增一個節點,就直接新增在連結串列的最後
  3. 示意圖:
  • 按序新增方法
    此方法在新增英雄時,根據英雄的排名將英雄新增到指定的位置。
  1. 首先找到新節點應該新增的位置,通過輔助指標來遍歷找到位置;
  2. 將新節點的next設定為指標指向節點的next節點;
  3. 將指標指向的節點的next設定新節點;(2、3步驟順序不能互換)
  4. 不能新增相同排名的英雄;
  5. 示意圖:
  • 修改節點方法
  1. 先通過遍歷找到對應節點;
  2. 修改資料。
  • 刪除節點方法
  1. 找到需要刪除的del節點;
  2. 將del節點的前一個節點的next指向del節點的next節點;
  3. 示意圖:

2.2 單連結串列的相關面試題

  • 求單連結串列中的有效節點的個數

思路:
遍歷單連結串列,如果有頭結點忽略頭結點

程式碼:

    /**
     * 獲取連結串列的長度
     *
     * @param head 連結串列頭結點
     * @return 長度
     */
    int getLength(HeroNode head) {
        int length = 0;
        while (Objects.nonNull(head.getNext())) {
            length++;
            head = head.getNext();
        }
        return length;
    }
  • 查詢單連結串列中的倒數第k個節點
    思路:
  1. 先求得連結串列的長度length;
  2. 用length - k即可算出倒數第k個節點所處位置;
  3. 從連結串列頭結點遍歷length - k次即可。

程式碼:

    /**
     * 尋找倒數第k個節點
     *
     * @param singleLinkedList 連結串列
     * @param k                k值
     * @return 倒數第k個節點
     */
    HeroNode findTheKthFromBottom(SingleLinkedList singleLinkedList, int k) {
        int length = getLength(singleLinkedList.head);
        int index = length + 1 - k;
        HeroNode temp = singleLinkedList.head;
        while (Objects.nonNull(singleLinkedList.head.getNext()) && index > 0) {
            temp = temp.getNext();
            index--;
        }
        return temp;
    }
  • 反轉連結串列
  1. 定義一個新的頭結點newHead;
  2. 遍歷原來的連結串列,每遍歷一個節點,就將這個節點放到newHead的next即可;
  3. 列印時以newHead為頭結點列印。

示意圖:

程式碼:

    /**
     * 反轉連結串列
     * 思路:
     * 定義一個新的頭結點newHead,遍歷連結串列,依次將遍歷的節點取出放到newHead的後面即可
     *
     * @param oldHead 連結串列的舊頭結點
     */
    void reverse(HeroNode oldHead) {
        // 定義一個新的頭結點
        HeroNode newHead = new HeroNode(0L, "", "");
        // 定義一個臨時節點,記錄即將遍歷的下一個節點
        HeroNode next;
        // 跳過頭節點
        HeroNode current = oldHead.getNext();
        while (Objects.nonNull(current)) {
            // 記錄即將遍歷的下一個節點
            next = current.getNext();
            // 將newHead的下一個節點設定為當前節點的next
            current.setNext(newHead.getNext());
            // 將newHead的next設定為當前節點
            newHead.setNext(current);
            // 恢復遍歷指標
            current = next;
        }
        // 修改連結串列頭結點
        head = newHead;
    }
  • 從尾到頭列印單連結串列
    思路:
    方式1:將單連結串列先進行反轉操作,然後再遍歷列印即可,缺點是會破壞原來的單連結串列的儲存結構;
    方式2:可以利用棧資料結構先進後出的特性,從頭到尾遍歷時將節點依次壓入棧中,列印時依次從棧中pop即可。

程式碼:

    /**
     * 逆向列印連結串列
     * 思路:
     * 1、先反轉連結串列,再列印連結串列,缺點是會破壞原連結串列的結構
     * 2、利用棧資料結構先進後出的特性來輔助實現
     * 這裡用棧來實現
     */
    void reversePrint() {
        Stack<HeroNode> nodeStack = new Stack<>();
        HeroNode current = head.getNext();
        while (Objects.nonNull(current)) {
            nodeStack.push(current);
            current = current.getNext();
        }
        while (!nodeStack.isEmpty()) {
            System.out.println(nodeStack.pop());
        }
    }

相關文章