數據存儲在「節點」中java
class Node { E e; Node next; }
鏈表與數組在添加元素方面有很大的不一樣。數組在末尾添加元素很簡單,而鏈表在頭部添加元素很簡單。緣由是:數組維護者size
,而鏈表維護者head
。原理以下:
![]()
public class LinkedList<E> { // 節點 private class Node { // 存儲的元素 public E e; // 下一個節點 public Node next; public Node(E e, Node node) { this.e = e; this.next = node; } public Node(E e) { this(e, null); } public Node() { this(null, null); } @Override public String toString() { return e.toString(); } } private Node head; private int size; public LinkedList() { head = null; size = 0; } public int getSize() { return size; } public boolean isEmpty() { return size == 0; } // 練習用:在鏈表index位置添加一個元素e public void add(E e, int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("index is illegal"); } if (index == 0) { // 頭部添加 addFirst(e); } else { // 插入 // 須要插入元素位置的上一個元素 Node prev = head; for (int i = 0; i < index - 1; i++) { // 讓prev指向插入元素的前一個元素 prev = prev.next; } // Node node = new Node(e); // node.next = prev.next; // prev.next = node; // 上面三句話等價於 prev.next = new Node(e, prev.next); size++; } } // 在鏈表頭部添加一個元素 public void addFirst(E e) { // Node node = new Node(e); // node.next = head; // head = node; // 上面三句話等價於 head = new Node(e, head); size++; } // 在鏈表尾部添加元素 public void addLast(E e) { add(e, size); } }
but
,有沒有發現,上面的代碼中有一個很不方便的地方,那就是咱們每次在add
操做的時候都會去作一次index
是否爲0
的判斷。
解決辦法:
若是每次add
操做,不用去判斷,而是直接添加就行了。咱們能夠增長一個虛擬頭節點!這個節點什麼都不作,僅僅是head
以前的那個節點。(是否是和循環隊列咱們故意浪費一個空間有點相似?)node
public class LinkedList<E> { // 節點 private class Node { // 存儲的元素 public E e; // 下一個節點 public Node next; public Node(E e, Node node) { this.e = e; this.next = node; } 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(E e, int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("index is illegal"); } // 須要插入元素位置的上一個元素 Node prev = dummyHead; for (int i = 0; i < index; i++) { // 讓prev指向插入元素的前一個元素 prev = prev.next; } prev.next = new Node(e, prev.next); size++; } // 在鏈表頭部添加一個元素 public void addFirst(E e) { add(e, 0); } // 在鏈表尾部添加元素 public void addLast(E e) { add(e, size); } }
修改:數組
// 練習用:在index位置上設置元素的值爲e public void set(int index, E e) { if (index < 0 || index > size) { throw new IllegalArgumentException("set failed, index is illegal"); } 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; }
查詢數據結構
// 練習用:獲取index位置的元素 public E get(int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("get failed, index is illegal"); } 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); } @Override public String toString() { StringBuilder res = new StringBuilder(); for (Node cur = dummyHead.next; cur != null; cur = cur.next) { res.append(cur.e + "->"); } res.append("NULL"); return res.toString(); }
// 練習用:刪除index位置上的元素 public E remove(int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("remove failed, index is illegal"); } // 要刪除節點的上一個節點 Node prev = dummyHead; for (int i = 0; i < index; i++) { prev = prev.next; } Node delNode = prev.next; prev.next = delNode.next; delNode.next = null; size--; return delNode.e; } // 刪除第一個元素 public E removeFirst() { return remove(0); } // 刪除最後一個元素 public E removeLast() { return remove(size - 1); }
添加操做app
addLast(e)
:O(n)
addFirst(e)
:O(1)
add(e, index)
:O(n/2) = O(n)
刪除操做ide
removeLast(e)
:O(n)
removeFirst(e)
:O(1)
remove(e, index)
:O(n/2) = O(n)
修改操做ui
set(index, e)
:O(n)
查找操做this
get(index)
:O(n)
contains(e)
:O(n)
綜上:spa
操做 | 複雜度 |
---|---|
增 | O(n) |
刪 | O(n) |
改 | O(n) |
查 | O(n) |
鏈表的效率那麼低,咱們爲何還要用鏈表? 若是咱們只對鏈表頭部進行增、刪、查操做呢?沒錯O(1)!這就是咱們用鏈表的緣由。