資料結構——【連結串列】
最基礎的動態資料結構:連結串列
Java中線性資料結構包括:陣列、棧、佇列【這三者底層都是基於動態陣列實現的,實現動態的機制依靠resize()動態擴容】、連結串列【真正的動態資料結構】。
連結串列可以分為單向連結串列和雙向連結串列。連結串列中的資料都儲存在Node節點中,連結串列與連結串列之間的連線依靠next指標
Class Node{
//存放資料
E e;
//指向當前節點的下一個節點
Node next;
}
為什麼說連結串列是真正的動態資料結構呢?
連結串列不像陣列,需要在初始化時候申明容量。連結串列中每一個節點都是一個Node物件,存放著資料和指向下一個Node的引用,在最後一個Node中,next為Null,連結串列不需要考慮和處理固定容量的問題,自然就不需要resize()了。
對於連結串列的特點,綜合而言:
優點:真正的動態資料結構,不需要處理固定容量的問題
缺點:不像陣列一樣可以通過Index直接定位元素,喪失了隨機訪問的能力,從底層來說,陣列開闢的空間在記憶體中是連續分佈的,所以我們可以直接通過index的偏移直接計算出響應資料所儲存的記憶體地址,直接通過O(1)即可取得元素。但是連結串列是通過next引用連線的,每一個Node所在的記憶體地址是不連續的,我們只能通過next來找到對應的元素。
下面我們來設計一個單向連結串列
使用了連結串列的虛擬頭節點dummyHead,這樣不需要對addFirst()進行特殊處理
package cn.itcats.linkedlist; public class LinkedList<E> { //建立內部類Node private class Node{ public E e; public Node next; public Node(E e , Node next){ this.e = e ; this.next = next; } public Node(E e){ this(e,null); } public Node(){ this(null,null); } @Override public String toString(){ return e.toString(); } } //為連結串列設定虛擬頭結點,則不需要對頭結點進行特殊處理 private Node dummyHead; private int size; public LinkedList(){ dummyHead = new Node(null,null); size = 0; } //獲得連結串列中元素個數 public int getSize(){ return size; } //判斷連結串列是否為空 public boolean isEmpty(){ return size == 0 ; } //在連結串列的index位置新增新元素e public void add(int index ,E e){ //對index進行判斷 if(index < 0 && index > size){ throw new IllegalArgumentException("index不合法"); } Node prev = dummyHead; //當遍歷到插入索引的前一個索引 for(int i = 0 ;i < index ; i++){ //prev指向下一個元素(index) prev = prev.next; } // Node node = new Node(e); // node.next = prev.next; // prev.next = node; prev.next = new Node(e,prev.next); size ++ ; } //獲取連結串列中指定"索引"的元素,所謂索引,只不過是遍歷位置的次數而已 public E get(int index){ if(index < 0 || index >= size){ throw new IllegalArgumentException("index不合法"); } //從dummyHead的下一個節點遍歷 Node cur = dummyHead.next; for(int i = 0 ; i < index ; i++){ cur = cur.next; } return cur.e; } //獲取連結串列中的第一個元素 public E getFirst(){ return get(0); } //獲取連結串列中的最後一個元素 public E getLast(){ return get(size-1); } //修改連結串列中第index個位置的元素為e public void set(int index, E e){ if(index < 0 || index >= size){ throw new IllegalArgumentException("index不合法"); } //找到第index位置的元素 Node cur = dummyHead.next; for(int i = 0 ; i < index ; i++){ cur = cur.next; } cur.e = e; } //查詢連結串列中是否存在元素e public boolean contains(E e){ Node cur = dummyHead.next; while(cur != null){ if(cur.e.equals(e)){ return true; } cur = cur.next; } return false; } /** * 在連結串列頭部新增新的元素 */ public void addFirst(E e){ add(0,e); } //在連結串列尾部新增元素 public void addLast(E e){ add(size,e); } //刪除節點 public E remove(int index){ if(index < 0 || index >= size){ throw new IllegalArgumentException("index不合法"); } Node prev = dummyHead; for(int i = 0 ; i < index ;i++){ //prev此時指向的是被刪除節點的前一個位置 prev = prev.next; } //需要被刪除的節點 Node removeNode = prev.next; prev.next = removeNode.next; removeNode.next = null; size --; return removeNode.e; } //從連結串列中刪除第一個元素,返回刪除的元素 public E removeFirst(){ return remove(0); } //從連結串列中刪除最後一個元素,返回刪除的元素 public E removeLast(){ return remove(size -1); } //遍歷連結串列 @Override public String toString(){ StringBuilder sb = new StringBuilder(); for(Node cur = dummyHead.next ; cur != null ; cur = cur.next) sb.append(cur+" -->"); sb.append("NULL"); return sb.toString(); } }
測試方法
package cn.itcats.linkedlist;
public class Test {
public static void main(String[] args) {
LinkedList<Integer> linked = new LinkedList<>();
for(int i = 0 ; i < 5 ; i++){
linked.addFirst(i);
System.out.println(linked);
}
linked.add(2,111);
System.out.println(linked);
linked.remove(3);
System.out.println(linked);
}
}
連結串列的時間複雜度分析
新增操作:
addLast(e) O(n)
addFirst(e) O(1)
add(index,e) O(n)
刪除操作:
removeLast(e) O(n)
removeFirst(e) O(1)
add(index,e) O(n)
修改操作 set(index,e) O(n)
查詢操作
get(index) O(n)
contains(e) O(n)