資料結構(2)-- 單向連結串列
阿新 • • 發佈:2018-12-13
目錄
0.目錄
1.線性表 – 陣列
2.線性表 – 單向連結串列
連結串列
連結串列是一種物理儲存單元上非連續、非順序的資料結構,資料元素的邏輯順序是通過連結串列中的指標連結次序實現的。連結串列由一系列節點組成,這些節點不必在記憶體中相連。每個節點由資料部分Data和鏈部分Next組成,Next指向下一個節點,這樣當新增或者刪除時,只需要修改相關節點的Next指向,效率很高)。
// 連結串列節點程式碼(使用內部類) private class Node<E> { private E element; private Node<E> next; public Node(E e, Node<E> next) { element = e; this.next = next; } public Node(E e) { this(e, null); } public Node() { this(null); } // 使用虛擬頭結點保持邏輯連貫性、使用尾節點改進效能 private Node<E> dummyHead; private Node<E> tail; private int size = 0; // 兩種構造方法 public Linked(E e) { dummyHead = new Node<>(); dummyHead.next = new Node<>(e); tail = dummyHead.next; size++; } public Linked() { dummyHead = new Node<>(); tail = dummyHead; } // 在指定索引位置插入元素 public void add(int index, E e) { if (index < 0 || index > size) { throw new IllegalArgumentException("Wrong index: " + index); } Node<E> pre = dummyHead; for (int i = 0; i < index; i++) { pre = pre.next; } pre.next = new Node<>(e, pre.next); if (index == size) { tail = pre.next; } size++; } // 順序遍歷連結串列(重寫toString方法) @Override public String toString() { StringBuilder sb = new StringBuilder("Linked head["); Node<E> cur = dummyHead; while (cur.next != null) { cur = cur.next; sb.append(cur.element).append("->"); } sb.append("NULL").append("]tail"); return sb.toString(); } // 逆序遍歷連結串列(遞迴思想) public void printDesc() { System.out.print("Linked tail[Null"); printDesc(dummyHead.next); System.out.println("]head"); } private void printDesc(Node<E> head) { if (head != null) { printDesc(head.next); System.out.print("<-" + head.element); } } // 單鏈表反轉 public void reverse() { // 非遞迴(效率高) dummyHead.next = reverse(dummyHead.next); // 遞迴 // dummyHead.next = reverseRecursion(dummyHead.next); } // 非遞迴實現 private Node<E> reverse(Node<E> head) { Node<E> pre = null; Node<E> cur = head; Node<E> next = null; while (cur != null) { next = cur.next; cur.next = pre; pre = cur; cur = next; } return pre; } // 遞迴實現 private Node<E> reverseRecursion(Node<E> node) { if (node == null) { return null; } if (node.next == null) { return node; } Node<E> next = node.next; node.next = null; Node<E> result = reverseRecursion(next); next.next = node; return result; }
上述程式碼簡單實現了基於單向連結串列實現的線性表的一些細節。單向連結串列實現的線性表,不需要擴容操作;引入了虛擬頭結點,使各方法在實現邏輯上會更加統一;引入了尾節點tail,使得對單鏈表的部分尾部操作效能顯著提升(O(n) -> O(1)),可顯著提升基於單向連結串列實現的其他資料結構的效能(如佇列、棧等,後續會介紹)。
除了上述單向連結串列,還有其他的實現方式,常見的有迴圈單向連結串列、雙向連結串列、迴圈雙向連結串列。其中,LinkedList集合類的實現就是雙向連結串列。
複雜度分析
- 增:add – O(n)、addFirst 和 addLast(tail實現) – O(1)
- 刪:remove – O(n)、removeFirst – O(1)
- 改:set – O(n)、setFirst 和 setLast(tail實現) – O(1)
- 查:get – O(n)、getFirst 和 getLast(tail實現) – O(1)
可以看出,基於單鏈表實現的線性表結構,特點是:中間操作慢、首尾操作快。
原始碼
public class Linked<E> { private class Node<E> { private E element; private Node<E> next; public Node(E e, Node<E> next) { element = e; this.next = next; } public Node(E e) { this(e, null); } public Node() { this(null); } @Override public String toString() { return element == null ? "null" : element.toString(); } } private Node<E> dummyHead; private Node<E> tail; private int size = 0; public Linked(Node<E> node) { dummyHead = new Node<>(); dummyHead.next = node; if (node == null) { tail = dummyHead; } else { tail = dummyHead.next; size++; } } public Linked(E e) { dummyHead = new Node<>(); dummyHead.next = new Node<>(e); tail = dummyHead.next; size++; } public Linked() { dummyHead = new Node<>(); tail = dummyHead; } /** * 在指定索引位置插入元素 O(n) * 特例:addFirst 和 addLast(tail實現) 為O(1) * * @param index * @param e */ public void add(int index, E e) { if (index < 0 || index > size) { throw new IllegalArgumentException("Wrong index: " + index); } Node<E> pre = dummyHead; for (int i = 0; i < index; i++) { pre = pre.next; } pre.next = new Node<>(e, pre.next); if (index == size) { tail = pre.next; } size++; } public void addFirst(E e) { add(0, e); } public void addLast(E e) { // 複用add方法,則時間複雜度為O(n) // add(size, e); // 使用tail節點實現,則時間複雜度為O(1) tail.next = new Node<>(e); tail = tail.next; size++; } /** * 刪除指定位置的元素 O(n) * 特例:removeFirst為O(1) * * @param index */ public void remove(int index) { if (index < 0 || index >= size) { throw new IllegalArgumentException("Wrong index: " + index); } Node<E> pre = dummyHead; for (int i = 0; i < index; i++) { pre = pre.next; } if (index == size - 1) { tail = pre; } // 使del節點能被JVM正常GC Node<E> del = pre.next; pre.next = del.next; del.next = null; size--; } public void removeFirst() { remove(0); } public void removeLast() { remove(size - 1); } /** * 更改指定索引位置的元素 O(n) * 特例:setFirst 和 setLast(tail實現) 為O(1) * * @param index * @param e */ public void set(int index, E e) { if (index < 0 || index >= size) { throw new IllegalArgumentException("Wrong index: " + index); } Node<E> cur = dummyHead.next; for (int i = 0; i < index; i++) { cur = cur.next; } cur.element = e; } public void setFirst(E e) { set(0, e); } public void setLast(E e) { // 複用add方法,則時間複雜度為O(n) // set(size - 1, e); // 使用tail節點實現,則時間複雜度為O(1) tail.element = e; } /** * 查詢指定索引位置的元素 O(n) * 特例:getFirst 和 getLast(tail實現) 為O(1) * * @param index * @return */ public E get(int index) { if (index < 0 || index >= size) { throw new IllegalArgumentException("Wrong index: " + index); } Node<E> cur = dummyHead.next; for (int i = 0; i < index; i++) { cur = cur.next; } return cur.element; } public E getFirst() { return get(0); } public E getLast() { return tail.element; } public int getSize() { return size; } public boolean isEmpty() { return size == 0; } /** * 查詢連結串列是否包含某個元素,如果包含返回索引,如果不包含返回-1 O(n) * * @param e * @return */ public int contains(E e) { if (e == null) { return -1; } Node<E> cur = dummyHead.next; for (int i = 0; i < size; i++) { if (e.equals(cur.element)) { return i; } cur = cur.next; } return -1; } /** * 反向列印連結串列,遞迴思想 */ public void printDesc() { System.out.print("Linked tail[Null"); printDesc(dummyHead.next); System.out.println("]head"); } private void printDesc(Node<E> head) { if (head != null) { printDesc(head.next); System.out.print("<-" + head.element); } } /** * 反轉連結串列 */ public void reverse() { // 非遞迴(效率高) dummyHead.next = reverse(dummyHead.next); // 遞迴 // dummyHead.next = reverseRecursion(dummyHead.next); } private Node<E> reverse(Node<E> head) { Node<E> pre = null; Node<E> cur = head; Node<E> next = null; while (cur != null) { next = cur.next; cur.next = pre; pre = cur; cur = next; } return pre; } // 遞迴實現 private Node<E> reverseRecursion(Node<E> node) { if (node == null) { return null; } if (node.next == null) { return node; } Node<E> next = node.next; node.next = null; Node<E> result = reverseRecursion(next); next.next = node; return result; } @Override public String toString() { StringBuilder sb = new StringBuilder("Linked head["); Node<E> cur = dummyHead; while (cur.next != null) { cur = cur.next; sb.append(cur.element).append("->"); } sb.append("NULL").append("]tail"); // sb.append(size); return sb.toString(); } }