鏈表一

鏈表

鏈表node

1、什麼是鏈表?

  1. 和數組同樣,鏈表也是一種線性表。
  2. 從內存結構來看,鏈表的內存結構是不連續的內存空間,是將一組零散的內存塊串聯起來,從而進行數據存儲的數據結構。
  3. 鏈表中的每個內存塊被稱爲節點Node。節點除了存儲數據外,還需記錄鏈上下一個節點的地址,即後繼指針next。

2、鏈表的特色

  1. 插入、刪除數據效率高,爲O(1)級別(只需更改指針指向便可),隨機訪問效率低O(n)級別(須要從鏈頭至鏈尾進行遍歷)。
  2. 和數組相比,內存空間消耗更大,由於每一個存儲數據的節點都須要額外的空間存儲後繼指針。

3、經常使用鏈表:單鏈表、循環鏈表和雙向鏈表

單鏈表

enter description here
1)每一個節點只包含一個指針,即後繼指針。
2)單鏈表有兩個特殊的節點,即首節點和尾節點。爲何特殊?用首節點地址表示整條鏈表,尾節點的後繼指針指向空地址null。
3)性能特色:插入和刪除節點的時間複雜度爲O(1),查找的時間複雜度爲O(n)。數組

循環鏈表

enter description here

1)除了尾節點的後繼指針指向首節點的地址外均與單鏈表一致。緩存

2)適用於存儲有循環特色的數據,好比約瑟夫問題。數據結構

//約瑟夫環運做以下:
  //一、一羣人圍在一塊兒坐成環狀(如:N)
  //二、從某個編號開始報數(如:K)
  //三、數到某個數(如:M)的時候,此人出列,下一我的從新報數
  //四、一直循環,直到全部人出列[3]  ,約瑟夫環結束
  public class yuesefu 
  {
      static class Node
      {
          int val;
          Node next;
          Node(int v)
          {
              val=v;
          }        
      }
      public static void main(String[] args) {
          int N=9;//表示總個數
          int M=5;//數到幾齣列
          
          //頭節點單列出來,方便造成循環鏈表
          Node t=new Node(1);
          Node x=t;
          
          //創建單向鏈表
          for(int i=2;i<=N;i++)
          {
              x=(x.next=new Node(i));
          }    
          
          //最後一個節點的next指向第一個節點,造成循環鏈表
          x.next=t;
          System.out.println("依次出來的順序爲:");
          while(x!=x.next)
          {
              for(int i=1;i<M;i++)
              {
                  x=x.next;
              }    
              System.out.print(x.next.val+" ");
              x.next=x.next.next;
          }
          System.out.println();
          System.out.println("最後剩餘的是: "+x.val);    
      }
  }

雙向鏈表

enter description here
1)節點除了存儲數據外,還有兩個指針分別指向前一個節點地址(前驅指針prev)和下一個節點地址(後繼指針next)。
2)首節點的前驅指針prev和尾節點的後繼指針均指向空地址。
3)性能特色:
和單鏈表相比,存儲相同的數據,須要消耗更多的存儲空間。
插入、刪除操做比單鏈表效率更高O(1)級別。以刪除操做爲例,刪除操做分爲2種狀況:給定數據值刪除對應節點和給定節點地址刪除節點。
對於前一種狀況,單鏈表和雙向鏈表都須要從頭至尾進行遍歷從而找到對應節點進行刪除,時間複雜度爲O(n)。
對於第二種狀況,要進行刪除操做必須找到前驅節點,單鏈表須要從頭至尾進行遍歷直到p->next = q,時間複雜度爲O(n),而雙向鏈表能夠直接找到前驅節點,時間複雜度爲O(1)。
對於一個有序鏈表,雙向鏈表的按值查詢效率要比單鏈表高一些。由於咱們能夠記錄上次查找的位置p,每一次查詢時,根據要查找的值與p的大小關係,決定是往前仍是日後查找,因此平均只須要查找一半的數據。性能

  • 雙向循環鏈表:首節點的前驅指針指向尾節點,尾節點的後繼指針指向首節點。

4、鏈表和哨兵

  • 利用哨兵簡化實現難度指針

    首先,咱們先回顧一下鏈表的插入和刪除操做。若是咱們在節點p後面加入一個新的節點,只須要下面兩行代碼就能搞定。code

    new_node->next = p->next;
    p->next = new_node;

    可是,當咱們要想一個空鏈表插入地一個節點時就不能這樣寫了。咱們須要特殊處理blog

    if(head==null){
        head = new_node;
    }

    這個時候,咱們引入一個不儲存數據的空節點,無論鏈表是否是空的,head都指向它,咱們把這種有哨兵節點的鏈表叫帶頭鏈表. 有了哨兵節點咱們就能統一實現插入刪除操做而不須要特殊處理了。排序

  • 利用哨兵實現有序鏈表的合併

    // 有序鏈表的合併
    /**
    *Definition for singly-linked list
    *public class ListNode{
    *    int val;
    *    ListNode next;
    *    ListNode(int x){ val=x; }
    * }
    */
    public ListNode mergeTwoLists(ListNode l1, ListNode l2){
        ListNode soldier = new ListNode(0);
        ListNode p = soldier;
    
        while( l1 != null && l2 != null){
            if( l1.val < l2.val ){
                p.next = l1;
                l1 = l1.next;
            }
            else{
                p.next = l2;
                l2 = l2.next;
            }
            p = p.next;
        }
    
        if(l1!=null) { p.next = l1; }
        if(l2!=null) { p.next = l2; }
        return soldier.next;
    }

    相似的應用有不少不只限於鏈表,在歸併排序等地方也有應用,等待咱們去發掘!

5、選擇數組仍是鏈表?

  1. 插入、刪除和隨機訪問的時間複雜度
    數組:插入、刪除的時間複雜度是O(n),隨機訪問的時間複雜度是O(1)。
    鏈表:插入、刪除的時間複雜度是O(1),隨機訪問的時間複雜端是O(n)。
  2. 數組缺點
    1)若申請內存空間很大,好比100M,但若內存空間沒有100M的連續空間時,則會申請失敗,儘管內存可用空間超過100M。
    2)大小固定,若存儲空間不足,需進行擴容,一旦擴容就要進行數據複製,而這時很是費時的。
  3. 鏈表缺點
    1)內存空間消耗更大,由於須要額外的空間存儲指針信息。
    2)對鏈表進行頻繁的插入和刪除操做,會致使頻繁的內存申請和釋放,容易形成內存碎片,若是是Java語言,還可能會形成頻繁的GC(自動垃圾回收器)操做。
  4. 如何選擇? 數組簡單易用,在實現上使用連續的內存空間,能夠藉助CPU的緩衝機制預讀數組中的數據,因此訪問效率更高,而鏈表在內存中並非連續存儲,因此對CPU緩存不友好,沒辦法預讀。 若是代碼對內存的使用很是苛刻,那數組就更適合。
相關文章
相關標籤/搜索