數據結構基礎(三)鏈表


鏈表的概念

我們知道數組是很常用的數據儲存方式,而鏈表就是繼數組之后,第二種最通用的數據儲存方式了。數組需要存放在連續的空間,計算機很容易實現。而鏈表的好處是不用確定空間長度,不夠的時候,直接申請新的節點,幫助插入。所以鏈表可以更靈活地進行內存分配。

鏈表(linked list)是一種序列形的數據結構,其中包含了很多通過鏈接 (link) 被串起來的節點。每個節點有一個數據域,儲存着節點的數值,還有一個指針域,指向下一個節點。

Linked List

簡單來說,鏈表是由一系列節點組成的,每個節點通過鏈接和其他節點鏈接。每個節點包含兩個屬性,一個是數值,記錄了節點的數據,另一個是指針,記錄了下一個節點的位置。正因為節點的指針能夠記錄其他節點的位置,所以我們不需要將節點按順序排列。

鏈表的類型

鏈表有很多不同的類型,但本質上都是一系列通過鏈接相連的節點。

單鏈表:鏈表中最簡單的一種是單向鏈表,它包含兩個域,一個信息域和一個指針域。這個鏈接指向列表中的下一個節點,而最后一個節點則指向一個空值。

Singly-linked-list.svg

雙鏈表:雙鏈表是為了解決鏈表節點不知道前面節點的尷尬,於是在節點的定義中,存在一個父節點,和一個子節點。這樣就能順着父節點往回找。

Doubly-linked-list.svg

循環鏈表:在一個 循環鏈表中, 首節點和末節點被連接在一起。這種方式在單向和雙向鏈表中皆可實現。

Circularly-linked-list.svg

塊狀鏈表:塊狀鏈表結合了數組和鏈表的特性,將連續成段的數組通過鏈接串起來。塊狀鏈表的特點是插入很靈活,尋找特定元素也比正常鏈表快速。

鏈表是一種重要的基礎數據結構,可以用來生成其它類型的數據結構,比如之后講到的堆、棧、樹和圖等等。單向鏈表也是鏈表中最基礎的類型,其它變種也是基於它的,在接下來的一部分,我會着重講解單向鏈表。

鏈表的基本操作

每種數據結構都有其對應的操作,而鏈表包含以下基本操作:

  1. 插入:將一個新元素插入鏈表的任意位置。
  2. 刪除:將一個元素從鏈表中刪除。
  3. 查找(遍歷):查找一個特定的元素。
  4. 更新:更新一個節點上的元素。

單向鏈表的實現

以下是鏈表的定義:

public class LinkedList {
    static class ListNode(
        int val; 
        ListNode next;
        public ListNode(int val) {
            this.val = val;
    }
    ListNode head; 
    ListNode tail;
    int size;
    public LinkedList() { 
        head = null;
        tail = null;
        size = 0;
    }
}

其中ListNode是節點的定義,其中的屬性val就是數據,而next就是下一個節點。而 LinkedList 則是單向鏈表類,其中包含頭節點head和尾節點tail。在初始化的時候,我們將頭尾節點都設為null,size初始化為0。

插入

在一個鏈表中插入新元素分為以下三種情況:

  • 插入到鏈表的最前頭,作為新的頭結點。
  • 插入到鏈表中間的位置。
  • 插入到鏈表的尾部,作為鏈表中最后的元素。

雖然新元素的插入位置不固定,但是鏈表插入的思想的固定的。插入只需要兩步:將新節點的next指針指向插入位置后的節點,再將插入節點前的next指針指向新插入的節點。

比如,我們在鏈表 {1, 2, 3, 4} 的基礎上分別實現在頭部、中間、尾部插入新元素5,過程如下圖所示:

鏈表中插入元素的 3 種情況示意圖

要注意的是,我們必須先執行步驟1,再執行步驟2;如果先執行步驟2,否則會導致插入位置后續的節點無法被找到。以下是代碼:

// insert the element to the specific position, position starts from index 0
public void insert(int position, int number) {
    if (position > size) {
        return;
    }
    ListNode newNode = new ListNode(number);
    if (position == 0) {
        newNode.next = head;
        head = newNode;
        if(tail == null) {
            tail = newNode;
        }
        size++;
    } else if (position == size) {
        this.append(number);
    } else {
        ListNode prev = head;
        for (int i = 0; i < position - 1; i++) {
            prev = prev.next;
        }
        ListNode next = prev.next;
        newNode.next = next;
        prev.next = newNode;
        size++;
    }
}
// append the new element to the end of the list
public void append(int number) { 
    ListNode newNode = new ListNode(number);
    if(tail == null) {
        tail = newNode;
    } else {
        tail.next = newNode;
        tail = newNode;
    }
    size++;
}

刪除

刪除元素如下圖所示,只要找到我們要刪除的節點,然后將前面節點的next指針指向被刪除節點的下一節點即可:

鏈表刪除元素示意圖
public void delete(int number) {
    if(head != null && head.val == number) { // delete the head node
        head = head.next;
        size--;
        if(size == 0) { // corner case: no element is left
            tail = head;
        }
    } else {
        ListNode prev = head;
        ListNode cur = head;
        while (prev != null && cur != null) {
            if (cur.val == number) {
                if(cur == tail) { // corner case: delete the last element
                    tail = prev;
                }
                prev.next = cur.next;
                size--;
                return;
            }
            prev = cur;
            cur = cur.next;
        }
    }
}

查找

查找元素比較簡單,只要從頭節點開始遍歷,找到與目標值相對應的節點,返回其位置即可:

public int search(int number) {
    ListNode cur = head;
    for(int index = 0; cur != null; index++) {
        if(cur.val == number) {
            return index;
        }
        cur = cur.next;
    }
    return -1;
}

更新

更新鏈表和查找相似,只要找到對應的節點后,改變節點的值即可:

public int update(int oldValue, int newValue) {
    ListNode cur = head;
    for(int index = 0; cur != null; index++) {
        if(cur.val == oldValue) {
            cur.val = newValue;
            return index;
        }
        cur = cur.next;
    }
    return -1;
}

LeetCode題目

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM