鏈表

1、什麼是鏈表

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

  • 相連的元素之間通過指針進行連接
  • 最後一個元素的後繼指針爲NULL
  • 在執行的過程中鏈表的長度可以增加或者是減少
  • 鏈表的空間可以按需進行分配
  • 內有內存空間的浪費,但是鏈表中的指針需要額外的內存開銷
    mark

2、鏈表的抽象數據類型

鏈表的主要操作:

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

鏈表的輔助操作

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

3、爲什麼需要使用鏈表

有許多數據結構可以像鏈表一樣做同樣的事情。在討論鏈表前,首先要了解鏈表和數組的區別,鏈表和數組都是可以用於存儲數據集合的。由於兩者的用途相同,所以需要對他們的的用法進去區別,需要了解什麼情況下使用數組,什麼情況下使用鏈表;

4、數組的概述

整個數組在內存中分配一塊連續的存儲空間。通過使用特定的元素的索引作爲數組下標,可以在常數時間內訪問數組元素。
mark
數組的特點是:
- 訪問速度快(常數時間)
- 簡單易用
數組訪問的缺點:
- 大小固定:數組的大小是靜態的(使用前指定數組的大小)
- 分配一個連續的空間:數組初始化分配空間時,有時無法分配能存儲整個數組的內存空間(數組的規模太大)
- 基於位置的插入操作複雜:如果在數組中指定的位置插入元素,則需要移動數組中其他的元素的位置

5、鏈表的優點

鏈表的優點是:他們可以在常數時間內擴展。當創建數組的時候,必須分配能存儲一定數量的內存,如果向數組中添加跟多的元素,那麼必須創建一個新的數組,然後把原數組中的元素複製到新數組中,這將花費大量的時間;
當然,可以通過爲數組預先分配一個很大的空間來防止上述的情況的發生。但是這個方法會因爲分配超過用戶需要的空間而造成內存的浪費。而對於鏈表,初始時僅需要分配一個元素的存儲空間,並且添加新的元素也很容易,不需要做任何內存複製和重新分配的操作

6、鏈表的缺點

主要體現在訪問單個元素的時間開銷問題。數組是隨機存取的,即存取數組中任意元素的時間開銷爲O(1)。而鏈表在最差情況下訪問一個元素的開銷爲O(n)。數組在存取時間方面的另外一個優點是內存的空間局部性。由於數組被定義爲連續的內存塊,所以任何數組元素與鄰居是物理相連的。這極大的得益於CPU的緩存模式。
儘管鏈表的動態存儲分配存儲空間有很大的優勢,但在存儲和檢索數據的開銷方面卻有很大的不足 。有時很難對鏈表進行操作。如果要刪除最後一項,倒數第二項必須更改後繼指針值爲NULL,這時需要從頭開始遍歷,找到倒數第二個結點的鏈接,並設置其後繼指針爲NULL

7、單鏈表

鏈表通常是指向單鏈表,它包含多個結點,每個結點有一個指向後繼元素的next(下一個)指針。表中的最後一個結點的next指針爲NULL,表示該鏈表的結束。
mark
下面是一個鏈表的聲明:

**
 - @Author: River
 - @Date:Created in  16:42 2018/5/28
 - @Description:
 */
public class ListNode {
    private int data;
    private ListNode next;

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

    public int getData() {
        return data;
    }

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

    public ListNode getNext() {
        return next;
    }

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

7.1、鏈表的基本操作

  • 遍歷鏈表
  • 插入元素
  • 刪除元素

7.2、鏈表的遍歷

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

  • 沿指針遍歷
  • 遍歷是顯示結點的內容(或計數)
  • 當next指針的值爲NULL時結束遍歷。
    mark
    ListLength()函數的輸入爲鏈表,其功能是統計鏈表中結點的個數。下面是函數的代碼,還可以添加額外的輸出函數來輸出鏈表中的數據
//統計結點的個數
    int ListLength(ListNode headNode) {
        int length=0;
        ListNode currentNode=headNode;
        while (currentNode!=null) {
            length++;
            currentNode=currentNode.getNext();

        }
        return length;
    }

時間複雜度爲O(n),用於掃描長度爲n的鏈表。空間複雜度爲O(1),僅僅用於創建臨時變量

7.3、單鏈表的插入操作

  • 在表頭插入一個新結點
  • 在表尾插入一個新結點
  • 在鏈表的中間插入一個結點

7.3.1、在單鏈表的開頭插入結點

若需要在表頭插入結點,值需要修改一個next指針(新結點的next指針),可以通過以下的兩個步驟:

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

7.3.2、在單鏈表的結尾插入結點

  • 新節點的next指針指向null
    mark
  • 最後一個結點的next指針指向新結點
    mark

7.3.3、在單鏈表的中間插入結點

如果要在位置3增加一個元素,則需要將指針定位於鏈表的位置2處。即需要從表頭開始經過兩個結點,然後插入新的結點
mark
位置結點的next指針指向新結點
mark

7.3.4、插入代碼如下

 ListNode insertLinkedList(ListNode headNode,ListNode nodeToInsert,int position) {
        if (headNode == null) {
            return nodeToInsert;
        }
        int size = ListLength(headNode);

        if (position > size + 1 || position < 1) {
            System.out.println("position of Node to insert is invalid,The valid inputs are 1 to " + (size + 1));
            return headNode;
        }
        if (position == 1){//在表頭插入
            nodeToInsert.setNext(null);
            headNode=nodeToInsert;
            return headNode;
        }else {
            //在表的中間或者末尾
            ListNode previousNode = headNode;
            int count=1;
            while (count < position - 1) {
                previousNode = previousNode.getNext();//找到需要插入位置的前一個結點,也就是位置結點
                count++;
            }
            ListNode currentNode = previousNode.getNext();//這個結點是插入位置的結點,如果position=size+1,則currentNode=null
            nodeToInsert.setNext(currentNode);
            previousNode.setNext(nodeToInsert);
        }
        return headNode;
    }

7.4、單鏈表的刪除

  • 在表頭刪除一個結點
  • 在表尾刪除一個結點
  • 在鏈表的中間刪除一個結點

7.4.1、刪除單鏈表的第一個結點

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

7.4.2、刪除單鏈表的最後一個結點

-遍歷鏈表,在遍歷時還要保存前驅結點的地址。當遍歷到鏈表的表尾時,將有兩個指針,分別保存表尾結點的指針tail(表尾)及指向表尾結點的前驅結點的指針
mark
- 將表尾的前驅結點的next指針更新爲NULL
mark
- 移除表尾結點
mark

7.4.3、刪除單鏈表的中間一個結點

  • 與上一種情況類似,在遍歷時保存前驅(前一次經過的)結點的地址,一旦找到要被刪除的結點,將前驅結點的next指針的值更新爲被刪除的結點的next值
    mark
  • 移除需要刪除的結點
    mark

7.4.4、刪除代碼如下

ListNode DeleteNodeFromLinkedList(ListNode headNode,int position){
        int size=ListLength(headNode);
        if (position > size || position < 1) {
            System.out.println("position of Node to delete is invalid,the valid inputs are 1 to " + (size));
        }
        if (position == 1) {
            ListNode currentNode = headNode.getNext();
            headNode = currentNode;
            return headNode;
        } else {//刪除中間或者是表尾結點
            ListNode previousNode=headNode;
            int count=1;
            while (count < position-1) {
                previousNode = previousNode.getNext();
                count++;
            }
            ListNode currentNode = previousNode.getNext();
            previousNode.setNext(currentNode.getNext());
            currentNode=null;
        } 
        return headNode;


    }

7.5、清空單向鏈表

void DeleteLinkedList(ListNode head) {
        ListNode auxilaryNode,iterator = head;
        while (iterator != null) {
            auxilaryNode = iterator.getNext();
            iterator = null;  //在java中垃圾回收器將自動處理
            iterator =auxilaryNode;
        }
    }

7.6、單向鏈表總結

單向鏈表是最常用也是基本的鏈表結構,需要熟練的掌握單向鏈表的增刪改查等基本的操作

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