數據結構-鏈表(1)


 

鏈表是一種用於存儲數據集合的數據結構。鏈表有以下幾個屬性:

  • 相鄰元素之間通過指針進行連接
  • 最后一個元素的后繼指針值為NULL
  • 在程序執行的過程中,鏈表的長度可以增加或縮小
  • 鏈表的空間能夠按需分配(直到系統內存耗盡)
  • 沒有內存空間的浪費(但是鏈表中的指針需要一些額外的內存開銷)

  

一、鏈表的分類

  鏈表大致可以分為這么幾類:

  1. 單向鏈表
  2. 雙向鏈表
  3. 存儲較為高效的雙向鏈表
  4. 循環鏈表
  5. 松散鏈表

二、LRU緩存淘汰算法

  在具體介紹鏈表之前,有必要先介紹一下關於LRU緩存技術:

  緩存是一種提高數據讀取性能的技術,在硬件設計,軟件開發中都有着非常廣泛的應用,比如常見的CPU緩存、數據庫緩存、瀏覽器緩存等等。

  緩存的大小是有限的,當緩存被用滿的時,此時就需要對緩存做相應的清理,但是,具體哪些數據應該被清理出去?哪些數據應該被保留?這就需要緩存淘汰策略來決定。常見的策略有三種:

  • 先進先出策略FIFO(First In ,First Out)
  • 最少使用策略LFU(Least Frequently Used)
  • 最近最少使用策略LRU(Least Recently Used)

 那么接下來就有一個問題:如何使用鏈表來實現LRU緩存淘汰策略呢?

三、鏈表的底層存儲結構

  相比於數組來說,鏈表是一種稍微復雜一點的數據結構,下面是鏈表和數組的內存分部對比圖:

  

  從圖中可以看出

  1. 對於數組來說,它需要一塊連續的內存存儲空間來存儲,對內存的要求比較高,也就是說,如果我申請100M大小的數組的話,當內存中沒有連續的、足夠大的存儲空間的時候,即便內存中剩余總可用空間大於100M,此時仍然會申請失敗
  2. 對於鏈表來說,它恰恰相反,它並不需要一塊連續的內存空間,它通過“指針”將一組零散的內存塊串聯起來使用,所以,如果我們申請的是100M大小的鏈表,當內存中剩余可用空間大於100M的時候,無論是否連續,申請都不會有問題。

四、鏈表的一些基本概念:

  1. 結點:鏈表是通過指針將自足零散的內存塊串聯在一起,所以,將每個內存塊就是鏈表的一個"結點"
  2. 后繼指針:每個鏈表的結點除了存儲數據之外,還需要記錄鏈路上下一個節點的地址,將記錄下一個結點地址的指針叫做后繼指針
  3. 頭結點:整個鏈路中第一個結點稱之為“頭結點
  4. 尾節點:整個鏈路中最后一個結點稱之為“尾節點
  5. 空地址NULL:當一個指針指向的不是下一個結點,而是空地址NULL,表示這是鏈路上的最后一個結點。

五、鏈表、數組的優缺點對比

  關於數組,其實在數組這一節,做了詳細的討論,這里不做多的贅述。

  鏈表與數組的優缺點對比:

  

  鏈表、數組與動態數組的時間復雜度對比:

    

 

單向鏈表

  鏈表通常是指單向鏈表,她包含了多個結點,每個結點有一個指向后繼元素的指針,表中最后一個結點next指針為NULL,表示該鏈表結束。

申明一個鏈表

public class ListNode {
    private int data;
    private ListNode next;

    public ListNode(int data) {
        this.data = data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public int getData() {
        return data;
    }

    public void setNext(ListNode next) {
        this.next = next;
    }

    public ListNode getNext() {
        return this.next;
    }
}

  鏈表的主要操作(時間復雜度均為O(1)):

  • 遍歷鏈表
  • 插入一個元素:插入一個元素到鏈表中
  • 刪除一個元素:移除並返回鏈表中指定位置的元素

  鏈表的輔助操作:

  • 刪除鏈表:移除鏈表中的所有元素(清空鏈表)
  • 計數:返回鏈表中元素的個數
  • 查找:尋找從鏈表表尾開始的第n個節點(node)

鏈表的遍歷

 假設表頭指針指向鏈表中的第一個結點。遍歷鏈表需要完成以下幾個步驟:

  • 沿指針遍歷
  • 遍歷時顯示節點的內容
  • 當next指針的值為NULL時,結束遍歷

  通過遍歷鏈表來對鏈表元素進行計數:

/**
     * 統計鏈表節點的個數
     */
    public int ListLength(ListNode headNode) {
        int length = 0;
        ListNode currentNode = headNode;
        while (currentNode != null) {
            length++;
            currentNode = currentNode.getNext();
        }
        return length;
    }

  此時間復雜度為O(n),用於掃描長度為n的鏈表。

  空間復雜度為O(1),僅用於創建臨時變量

單向鏈表的插入

  單向鏈表的插入可以分為以下3種情況

  • 在鏈表的頭前插入一個新結點(鏈表的開始出)
  • 在鏈表的尾后插入一個新結點(鏈表的結尾出)
  • 在鏈表的中間插入一個新結點(隨機位置)

在單向鏈表的開頭插入結點

  若需要在表頭節點前插入一個新結點,只需要修改一個next指針,可通過如下兩步完成:

  • 更新新節點next指針,是其指向當前結點的表頭節點。

   

  • 更新表頭指針的值,使其指向新結點。

    

在單向量表的結尾插入結點

  如果需要在表尾部插入新結點,則需要修改兩個next指針

  • 新結點的next指針指向NULL

 

  • 最后一個結點的指針指向新結點

   

在單向鏈表的中間插入結點

  假設給定插入新結點的位置,在這種情況下,需要修改兩個next指針:

  • 如果位置3增加一個元素,則需要將指針定位於鏈表的位置2,。即需要從表頭開始經過兩個結點,然后插入新結點。假設第二個結點為位置結點,新結點的next指針指向位置結點(我們要在此處增加新結點)的下一個結點

     

  • 位置結點的next指針指向新結點

      

代碼實現:

/**
     * 單向鏈表List節點進行插入操作
     */
    public ListNode InsertInLinkedList(ListNode headNode, ListNode nodeToInsert, int position) {
        //如果鏈表為空,則插入的節點即為頭結點
        if (headNode == null) {
            return nodeToInsert;
        }
        //獲取該鏈表的節點數
        int size = ListLength(headNode);
        if (position < 1 || position > size + 1) {
            System.out.println("Position of node to insert is invalid.The valid input are 1 to "
                    + (size + 1));
            return headNode;
        }
        //否則,插入元素要么是在頭插入,要么是在尾節點,或是中間
        if (position == 1) {
            nodeToInsert.setNext(headNode);
            return nodeToInsert;
        } else {
            //在鏈表的中間或尾部插入
            ListNode previousNode = headNode;
            int count = 1;
            while (count < position - 1) {
                previousNode = previousNode.getNext();
                count++;
            }
            ListNode currentNode = previousNode.getNext();
            nodeToInsert.setNext(currentNode);
            previousNode.setNext(nodeToInsert);
        }

        return headNode;
    }

  時間復雜度為O(n)。在最壞情況下,可能需要在鏈表尾部插入結點。

  空間復雜度為O(1),僅用於創建一個臨時變量。

單向鏈表的刪除

  單向鏈表的刪除操作,也分為三種情況:

  • 刪除鏈表的表頭(第一個)結點
  • 刪除鏈表的表尾(最后一個)節點
  • 刪除鏈表的中間的節點

刪除單向鏈表表頭結點

  刪除鏈表的第一個結點,可以通過兩步實現:

  • 創建一個臨時結點,它指向表頭指針所指的結點。

    

 

  • 修改表頭指針的值,使其指向下一個結點,並移除臨時結點。

     

刪除單向鏈表的最后一個結點

  這種情況下,操作比刪除第一個結點要麻煩一點,因為算法需要找到表尾節點的前驅節點。這需要三步來實現:

  • 遍歷鏈表,在遍歷時還要保存前驅(前一次經過)結點的地址。當遍歷到鏈表的表尾時,將有兩個指針,分別是表尾結點的指針tail(表尾)即指向表尾結點的錢去結點的指針

     

  • 將表尾的前驅節點的next指針更新為NULL

       

  • 移除表尾節點。

       

刪除單向鏈表中間一個結點

  在這種情況下,刪除的結點總是位於兩個結點之間,因此不需要更新表頭和表尾的指針。該刪除操作通過兩步實現:

  • 在遍歷時保存前驅(前一次經過的)結點的地址。一旦找到被刪除的結點,將前驅結點next指針的值更新為被刪除結點的next指針的值

      

 

  • 移除需要刪除的當前結點

       

代碼實現:

/**
     * 單向鏈表List的刪除操作
     */
    public ListNode deleteNodeFromeLinkedList(ListNode headNode, int position) {
        int size = ListLength(headNode);
        if (position > size || position < 1) {
            System.out.println("Postition of node to delete is invalid.The valid inputs are 1 to "
                    + size);
            return headNode;
        }
        if (position == 1) {
            ListNode currentNode = headNode.getNext();
            headNode = null;
            return currentNode;
        } else {
            ListNode preivousNode = headNode;
            int count = 1;
            while (count < position) {
                preivousNode = preivousNode.getNext();
                count++;
            }
            ListNode currentNode = preivousNode.getNext();
            preivousNode.setNext(currentNode.getNext());
            currentNode = null;
        }
        return headNode;
    }

  時間復雜度為O(n)。在最差情況下,可能需要刪除鏈表的表尾節點。

  空間復雜度為O(1),僅用於創建一個臨時變量

刪除單向鏈表

  該操作通過將當前結點存儲在臨時變量中,然后釋放當前結點(空間)的方式來完成。當時放完當前結點(空間)后,移動到下一個結點並將其存儲在臨時變量中,然后不斷重復該過程直至釋放所有結點。

代碼實現:

/**
     * 刪除單向鏈表
     */
    public void deleteLinkedList(ListNode headNode) {
        ListNode auxilaryNode,iterator = headNode;
        while (iterator != null){
            auxilaryNode = iterator.getNext();
            iterator = null;
            iterator = auxilaryNode;
        }
    }

  時間復雜度為O(n),用掃描大小為n的整個建鏈表

  空間復雜度為O(1),用於創建臨時變量


免責聲明!

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



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