圖解數據結構(02) -- 鏈表

1、什麼是鏈表

單向鏈表

鏈表(linkedlist)是一種在物理上非連續、非順序的數據結構,由若干節點(node)所組成;單向鏈表的每一個節點又包含兩部分,一部分是存放數據的變量 data,另一部分是指向下一個節點的指針 next
結構圖:
單向鏈表
代碼實現:

    private static class Node {
        int data;
        Node next;
    }

鏈表的第1個節點被稱爲頭節點,最後1個節點被稱爲尾節點,尾節點的 next 指針指向空;
與數組按照下標來隨機尋找元素不同,對於鏈表的其中一個節點A,只能根據節點A的 next 指針來找到該節點的下一個節點B,再根據節點B的next指針找到下一個節點C……一級一級,單線傳遞!想讓每個節點都能回溯到它的前置節點,可以使用雙向鏈表

雙向鏈表

雙向鏈表比單向鏈表稍微複雜一些,它的每一個節點除了擁有data和next指 針,還擁有指向前置節點的prev指針;
在這裏插入圖片描述

鏈表的存儲方式

說數組在內存中的存儲方式是順序存儲,那麼鏈表在內存中的存儲方式則是隨機存儲
數組在內存中佔用了連續完整的存儲空間,而鏈表則採用了見縫插針的方式,鏈表的每一個節點分佈在內存的不同位置,依靠 next 指針關聯起來,這樣可以靈活有效地利用零散的碎片空間。

  • 數組的內存分配方式圖:
    數組的內存分配方式
  • 鏈表的內存分配方式圖:
    鏈表的內存分配方式
    圖中的箭頭代表鏈表節點的 next 指針

2、鏈表的基本操作

【1】查找節點

在查找元素時,鏈表不像數組那樣可以通過下標快速進行定位,只能從頭節點開始向後一個一個節點逐一查找。
例如給出一個鏈表,需要查找從頭節點開始的第3個節點:
在這裏插入圖片描述
查找步驟:

  • 第1步,將查找的指針定位到頭節點
    在這裏插入圖片描述
  • 第2步,根據頭節點的next指針,定位到第2個節點
    在這裏插入圖片描述
  • 第3步,根據第2個節點的next指針,定位到第3個節點,查找完畢
    在這裏插入圖片描述

鏈表中的數據只能按順序進行訪問,最壞的時間複雜度是O(n)

【2】更新節點

如果不考慮查找節點的過程,鏈表的更新過程會像數組那樣簡單,直接把舊數據替換成新數據即可
在這裏插入圖片描述

【3】插入節點

鏈表插入節點時,分爲3種情況:

  • 尾部插入
    尾部插入把最後一個節點的next指針指向新插入的節點即可
    尾部插入
  • 頭部插入
    頭部插入可以分成兩個步驟:
    第1步,把新節點的next指針指向原先的頭節點
    第2步,把新節點變爲鏈表的頭節點
    在這裏插入圖片描述
  • 中間插入
    中間插入同樣分爲兩個步驟:
    第1步,新節點的 next 指針,指向插入位置的節點
    第2步,插入位置前置節點的 next 指針,指向新節點
    在這裏插入圖片描述
    只要內存空間允許,能夠插入鏈表的元素是無窮無盡的,不需要像數組那樣考慮擴容的問題

【4】刪除元素

鏈表的刪除操作同樣分爲3種情況:

  • 尾部刪除
    尾部刪除把倒數第2個節點的 next 指針指向空即可:
    在這裏插入圖片描述
  • 頭部刪除
    頭部刪除把鏈表的頭節點設爲原先頭節點的next指針即可:
    在這裏插入圖片描述
  • 中間刪除
    中間刪除把要刪除節點的前置節點的 next 指針,指向要刪除元素的下一個節點即可:
    在這裏插入圖片描述
    這裏需要注意的是,許多高級語言,如Java,擁有自動化的垃圾回收機制,所以不用刻意去釋放被刪除的節點,只要沒有外部引用指向它們,被刪除的節點 會被自動回收

鏈表的插入和刪除操作中如果不考慮插入、刪除操作之前查找元素的過程,只考慮純粹的插入和刪除操作,時間複雜度都是O(1)

實現鏈表的完整代碼:

public class MyLinkedList {
    // 頭節點指針
    private Node head;
    // 尾節點指針
    private Node last;
    // 鏈表實際長度
    private int size;
    //鏈表插入元素; data插入元素 ;index插入位置
    public void insert(int data, int index) throws Exception{
        if (index<0 || index>size) {
            throw new IndexOutOfBoundsException(" 超出鏈表節點範圍!");
        }
        Node insertedNode = new Node(data);
        if(size == 0){
            //空鏈表
            head = insertedNode;
            last = insertedNode;
        } else if(index == 0){
            //插入頭部
            insertedNode.next = head;
            head = insertedNode;

        }else if(size == index){
            //插入尾部
            last.next = insertedNode;
            last = insertedNode;
        }else {
            //插入中間
            Node prevNode = get(index-1);
            insertedNode.next = prevNode.next;
            prevNode.next = insertedNode;
        }
        size++;
    }

    //鏈表刪除元素 ; index刪除的位置
    public Node remove(int index) throws Exception{
        if (index<0 || index>=size) {
            throw new IndexOutOfBoundsException(" 超出鏈表節點範圍!");
        }
        Node removedNode = null;
        if(index == 0){
            //刪除頭節點
            removedNode = head;
            head = head.next;
        }else if(index == size-1){
            //刪除尾節點
            Node prevNode = get(index-1);
            removedNode = prevNode.next;
            prevNode.next = null;
            last = prevNode;
        }else {
            //刪除中間節點
            Node prevNode = get(index-1);
            Node nextNode = prevNode.next.next;
            removedNode = prevNode.next;
            prevNode.next = nextNode;
        }
        size--;
        return removedNode;
    }

    //鏈表查找元素  ; index查找的位置
    public Node get(int index) throws Exception {
        if (index<0 || index>=size) {
            throw new IndexOutOfBoundsException(" 超出鏈表節點範圍!");
        }
        Node temp = head;
        for(int i=0; i<index; i++){
            temp = temp.next;
        }
        return temp;
    }

    // 輸出鏈表
    public void output(){
        Node temp = head;
        while (temp!=null) {
            System.out.println(temp.data);
            temp = temp.next;
        }
     }

    // 鏈表節點
    private static class Node {
        int data;
        Node next;
        Node(int data) {
            this.data = data;
        }
     }

    public static void main(String[] args) throws Exception {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.insert(3,0);
        myLinkedList.insert(7,1);
        myLinkedList.insert(9,2);
        myLinkedList.insert(5,3);
        myLinkedList.insert(6,1);
        myLinkedList.remove(0);
        myLinkedList.output();
    }
}

輸出:
在這裏插入圖片描述
以上是對單鏈表相關操作的代碼實現。爲了尾部插入的方便,代碼中額外增加 了指向鏈表尾節點的指針 last

3、數組VS鏈表

數組和鏈表相關操作的性能對比如下圖:
在這裏插入圖片描述
數組的優勢在於能夠快速定位元素,對於讀操作多、寫操作少的場景來說,用數組更合適一些;
鏈表的優勢在於能夠靈活地進行插入和刪除操作,如果需要在尾部頻繁插入、刪除元素,用鏈表更合適一些。

—————————————————————————————————————————
內容來源:《漫畫算法》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章