【Java演示】什麼是鏈表?數據結構(三)

鏈表:隨機存儲,順序訪問(讀取)

前言

專欄《兩週幹掉數據結構》

本博文的大部分插圖來自於《漫畫算法——小灰》,也複製了該書部分文字
我加了一些自己的總結、代碼(我代碼實現是參考了本書以及java自帶LinkedList的源代碼)
我發現本書有關鏈表的代碼存在錯誤,已經向作者反饋
建議有能力的同學直接去看java自帶LinkedList的源代碼,寫的真的好
應本書作者要求,加上本書公衆號《程序員小灰》二維碼
在這裏插入圖片描述

一、單向鏈表

在這裏插入圖片描述
鏈表(linked list)是一種在物理上非連續、非順序的數據結構,由若干節點(node)所組成。
單向 鏈表的每一個節點又包含兩部分,一部分是存放數據的變量data,另一部分是指向下一個節點的指針next。
鏈表的第1個節點被稱爲頭節點,最後1個節點被稱爲尾節點,尾節點的next指針指向空。

什麼叫隨機存儲呢?

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

鏈表的基本操作

1. 查找節點

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

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

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

2. 更新節點

在這裏插入圖片描述
如果不考慮查找節點的過程,鏈表的更新過程會像數組那樣簡單,直接把舊數據替換成新數據即可。
如果不考慮查找元素的過程,只考慮純粹的更新節點操作,時間複雜度是O(1)

/**
     * 更新節點 將列表中指定位置的節點的data替換爲指定的data。
     *
     * @param index 需要更新的節點的位置
     * @param data  新data
     * @return 舊data
     */
    public int set(int index, int data) {
        Node x = get(index);
        int oldVal = x.data;
        x.data = data;
        return oldVal;
    }

3. 插入節點

只要內存空間允許,能夠插入鏈表的元素是無窮無盡的,不需要像數組那樣考慮擴容的問題。

與數組類似,鏈表插入節點時,同樣分爲3種情況。

  • 尾部插入
  • 頭部插入
  • 中間插入
3.1. 尾部插入

尾部插入,是最簡單的情況,把最後一個節點的next指針指向新插入的節點即可。
在這裏插入圖片描述

3.2. 頭部插入

頭部插入,可以分成兩個步驟。

  1. 第1步,把新節點的next指針指向原先的頭節點。
  2. 第2步,把新節點變爲鏈表的頭節點。
    在這裏插入圖片描述
3.3. 中間插入

中間插入,同樣分爲兩個步驟。

  1. 第1步,新節點的next指針,指向插入位置的節點。
  2. 第2步,插入位置前置節點的next指針,指向新節點。
    在這裏插入圖片描述

三鍾情況的代碼合到一起

/**
     * 鏈表插入元素
     *
     * @param index 插入位置
     * @param data  插入元素 被插入的鏈表節點的數據
     */
    public void insert(int index, int data) {
        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 prvNode = get(index - 1);
            insertedNode.next = prvNode.next;
            prvNode.next = insertedNode;
        }
        size++;
    }

4. 刪除元素

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

  1. 尾部刪除
  2. 頭部刪除
  3. 中間刪除
4.1. 尾部刪除

尾部刪除,是最簡單的情況,把倒數第2個節點的next指針指向空即
可。
在這裏插入圖片描述

4.1. 頭部刪除

頭部刪除,也很簡單,把鏈表的頭節點設爲原先頭節點的next指針即可。
在這裏插入圖片描述

4.1. 中間刪除

中間刪除,同樣很簡單,把要刪除節點的前置節點的next指針,指向要
刪除元素的下一個節點即可。
在這裏插入圖片描述
這裏需要注意的是,許多高級語言,如Java,擁有自動化的垃圾回收機制,所以我們不用刻意去釋放被刪除的節點,只要沒有外部引用指向它們,被刪除的節點會被自動回收。
如果不考慮插入、刪除操作之前查找元素的過程,只考慮純粹的插入和刪除操作,時間複雜度都是O(1)

/**
     * 鏈表刪除元素
     *
     * @param index 刪除的位置
     * @return 被刪除的節點
     */
    public Node remove(int index) {
        if (index < 0 || index > size) {
            throw new IndexOutOfBoundsException("超出鏈表節點範圍");
        }
        Node removeNode;
        if (index == 0) {
            if (size == 0) {
                throw new NullPointerException("當前鏈表爲空,不可以進行刪除操作");
            }
            //刪除頭節點
            removeNode = head;
            head = head.next;
        } else if (index == size - 1) {
            //刪除尾節點
            Node preNode = get(index - 1);
            removeNode = preNode.next;
            preNode.next = null;
            last = preNode;
        } else {
            //刪除中間節點
            Node prevNode = get(index - 1);
            removeNode = prevNode.next;
            prevNode.next = prevNode.next.next;
        }
        size--;
        return removeNode;
    }

Java實現鏈表的完整代碼

package chapter2.part2;

/**
 * Created by IntelliJ IDEA.
 *
 * @Author: 張志浩  Zhang Zhihao
 * @Email: [email protected]
 * @Date: 2020/5/3
 * @Time: 13:39
 * @Version: 1.0
 */
public class MyLinkedList2 {
    private Node head; //頭節點
    private Node last; //尾節點
    private int size; //鏈表實際長度

    public static void main(String[] args) {
        MyLinkedList2 myLinkedList = new MyLinkedList2();
//        myLinkedList.remove(0); // java.lang.NullPointerException: 當前鏈表爲空,不可以進行刪除操作
//        myLinkedList.remove(3); // java.lang.IndexOutOfBoundsException: 超出鏈表節點範圍
        myLinkedList.insert(0, 3);
        myLinkedList.insert(1, 7);
        myLinkedList.insert(2, 9);
        myLinkedList.insert(3, 5);
        myLinkedList.insert(1, 6);
        myLinkedList.remove(0);
        myLinkedList.set(0, 23);
        myLinkedList.output();
    }

    /**
     * 鏈表插入元素
     *
     * @param index 插入位置
     * @param data  插入元素 被插入的鏈表節點的數據
     */
    public void insert(int index, int data) {
        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 prvNode = get(index - 1);
            insertedNode.next = prvNode.next;
            prvNode.next = insertedNode;
        }
        size++;
    }

    /**
     * 鏈表刪除元素
     *
     * @param index 刪除的位置
     * @return 被刪除的節點
     */
    public Node remove(int index) {
        if (index < 0 || index > size) {
            throw new IndexOutOfBoundsException("超出鏈表節點範圍");
        }
        Node removeNode;
        if (index == 0) {
            if (size == 0) {
                throw new NullPointerException("當前鏈表爲空,不可以進行刪除操作");
            }
            //刪除頭節點
            removeNode = head;
            head = head.next;
        } else if (index == size - 1) {
            //刪除尾節點
            Node preNode = get(index - 1);
            removeNode = preNode.next;
            preNode.next = null;
            last = preNode;
        } else {
            //刪除中間節點
            Node prevNode = get(index - 1);
            removeNode = prevNode.next;
            prevNode.next = prevNode.next.next;
        }
        size--;
        return removeNode;
    }

    /**
     * 更新節點 將列表中指定位置的節點的data替換爲指定的data。
     *
     * @param index 需要更新的節點的位置
     * @param data  新data
     * @return 舊data
     */
    public int set(int index, int data) {
        Node x = get(index);
        int oldVal = x.data;
        x.data = data;
        return oldVal;
    }

    /**
     * 鏈表查找元素
     *
     * @param index 查找的位置
     * @return index位置的Node對象
     */
    public Node get(int index) {
        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.print(temp.data + " ");
            temp = temp.next;
        }
    }

    /**
     * 鏈表節點
     */
    class Node {
        int data;
        Node next;

        Node(int data) {
            this.data = data;
        }
    }
}



二、雙向鏈表

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

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