1. 程式人生 > >單向連結串列的實現(不設立虛擬頭節點)

單向連結串列的實現(不設立虛擬頭節點)

(希望我所描述的,給你帶來收穫!)——關於閱讀筆者資料結構系列,建議先將程式碼粘至IDE,然後對照文字解釋進行理解

開始丟擲——什麼是連結串列?

答:連結串列Linked list)是一種常見的基礎資料結構,是一種線性表,但是並不會按線性的順序儲存資料,而是在每一個節點裡存到下一個節點的指標(Pointer)。由於不必須按順序儲存,連結串列在插入的時候可以達到O(1)的複雜度,比另一種線性表順序表快得多,但是查詢一個節點或者訪問特定編號的節點則需要O(n)的時間,而順序表相應的時間複雜度分別是O(logn)和O(1)——————(摘自維基百科)

 

我的補充:連結串列具有真正意義上的動態

!而動態陣列是通過擴容縮容的手段達到一定程度的動態!連結串列使用“節點”去儲存資料,插入資料時就動態的申請節點!

 

我們首要思考的是,我們應該用什麼樣的“節點”去儲存資料,在這裡我採用的是私有內部類的設計——內部類相關知識傳送門(待補充!):

 

節點程式碼如下:

 1 public class LinkedList<E> {
 2     private class Node {
 3         public E e;  //資料域
 4         public Node next;  //引用域(java中不存在指標說法,但也可以理解成指標)
5 6 public Node() { 7 this(null,null); 8 } 9 10 public Node(E e) { 11 this.e = e; 12 this.next = null; 13 } 14 15 public Node(E e,Node next) { 16 this.e = e; 17 this.next = next; 18 } 19
20 @Override 21 public String toString() { 22 return e.toString(); 23 } 24 }
  }

 整體程式碼的補充:

 1 public class LinkedList<E> {
 2     private class Node {
 3         public E e;
 4         public Node next;
 5 
 6         public Node() {
 7             this(null,null);
 8         }
 9 
10         public Node(E e) {
11             this.e = e;
12             this.next = null;
13         }
14 
15         public Node(E e,Node next) {
16             this.e = e;
17             this.next = next;
18         }
19 
20         @Override
21         public String toString() {
22             return e.toString();
23         }
24     }
25     private Node head;
26     private int size;
27 
28     public int getSize() {
29         return size;
30     }
31 
32     public boolean isEmpty() {
33         return size == 0;
34     }
35 
36     public void addFirst(E e) {
37         head = new Node(e,head);
38         size ++;
39     }
40 
41     public void add(int index, E e) {
42         if (index < 0 || index > size)
43             throw new IllegalArgumentException("index is illegal");
44         if (index == 0)
45             addFirst(e);
46         else {
47             Node prev = head;
48             for (int i = 0; i < index - 1; i++) {
49                 prev = prev.next;
50             }
51             prev.next = new Node(e,prev.next);
52             size ++;
53         }
54     }
55 
56     public void addLast(E e) {
57         add(size,e);
58     }
59 
60     //測試列印輸出
61     @Override
62     public String toString() {
63         StringBuilder stringBuilder = new StringBuilder();
64         stringBuilder.append(String.format("LinkedList size = %d\n",size));
65         stringBuilder.append("head [");
66         Node cur = head;
67         for (int i = 0; cur != null; cur = cur.next) {
68             stringBuilder.append(cur);
69             if (cur.next != null)
70                 stringBuilder.append("->");
71         }
72         stringBuilder.append("] tail");
73         return String.valueOf(stringBuilder);
74     }
75 
76     public static void main(String[] args) {
77         LinkedList<Integer> linkedList = new LinkedList<>();
78         for (int i = 0; i < 10; i++) {
79             linkedList.addLast(i);
80         }
81         System.out.println(linkedList.toString());
82     }
83 }

  關於addFirst方法的闡述:

這裡我們首先設立一個頭節點(head)的概念,在這裡!如果連結串列有資料,頭節點其實就是連結串列頭的那個節點(如果連結串列沒有資料呢?那麼null就是該連結串列的“表頭”)

1、addFirst也就是說,在連結串列的第一個位置插入一個數據元素!

2、想要在表頭插入一個元素e,必須先建立一個節點去承載   ---->>>Node  node = new Node(e);

3、因為這個節點要當表頭了,所以它要放在head的前面  ---->>> node.next = head;

4、這個節點成為新的表頭了,所以head這個名字要交給這個新的節點 ---->>> head = node;

5、size是連結串列長度的變數,新增節點成功,則size ++

總結以上:三段程式碼可以直接濃縮為  head = new Node(e,head); ------->這和Node類的雙參建構函式有關

 

  關於add(int index, E e)方法的闡述:

要強調的是,這裡的index是我們人為意識去強加給連結串列的(目的是我們在底層操作的時候,更便於存取資料)——陣列具有索引(index),可以通過索引值直接取資料,但連結串列不可以!

很自然的,add方法是說:我們要在index位置上儲存元素e

第一步:我們需要找到 index - 1的那個節點,因為每一個節點只有指向下一個節點的“引用域”(這是單向連結串列)

 

第二步:index - 1位置的節點我們稱為 prev,待插入的節點稱為 cur,我們需要操作的是----->>>cur.next = prev.next;

(prev是(index - 1)位置的節點,那 prev.next 就是index位置的節點了,cur 需要替換 prev.next 的位置得先像 prev 學習,cur.next 要成為index位置上的節點)

 

第三步:當cur.next在index位置上,要想明白的是prev.next也在index位置上 執行----->>> prev.next = cur;(prev.next 始終是index位置節點,我把cur放在index位置上,那自然的cur.next 就是index + 1上的位置了——插入了新節點cur!)

 

第四步:新增節點  我們要維護我們的size變數------->>>size ++

總結來說:三個步驟依然可以用-------->prev.next = new Node(e,prev.next);這樣的形式來表達!

 

在闡述add方法的時候,我用了index - 1位置這樣的描述,認真閱讀了程式碼的朋友會發現add方法中for迴圈的條件語句是這樣的:

for (int i = 0; i < index - 1; i++) {
                prev = prev.next;
            }

 該迴圈表明的是,當 i == index - 2是for的最後一次迴圈;那index - 2才是待插入位置的前一個位置(prve)?

答:前面闡述的add方法中,筆者只是借用index、index - 1  來表明邏輯上的前後關係、因為實際上在不設立虛擬節點的前提下,我們預設表頭的第一個節點就是head節點!  迴圈體中 prev = prev.next; 語句, 假使 i == 0,第一次迴圈就已經使得prev在第二個位置上了;假設index可以取-1(-1是0的前一個節點,要在-1 和 0之間插入資料),如果i == 0,prev實則在index為1的位置,要使得插入成功,prev必須往回走兩步才能走到-1得位置,因此就是相差2,index - 2的邏輯來得自然(-1這種思考方式個人認為很奇特,讀者可以換種思路——比如在index == 3位置上插入節點;可以畫圖理解)