java演算法篇之三:連結串列
阿新 • • 發佈:2020-07-24
1. 簡介
前面已經實現了動態陣列、棧、佇列三種線性資料結構,但其底層都是依託靜態陣列,靠resize解決固定容量問題。
連結串列是一種物理儲存單元上非連續、非順序的儲存結構,資料元素的邏輯順序是通過連結串列中的指標連結次序實現的。是一種真正的動態資料結構
-
資料儲存在“節點”(Node)中,一個節點包含至少兩部分,一部分儲存真正的資料,另一部分是指向其他節點的指標。當某個節點的指標指向空節點(NULL)則表明該節點為尾節點
class Node { public E e; public Node next; }
-
優點:真正的動態,不需要處理固定容量的問題
-
缺點:喪失了隨機訪問的能力,無法像陣列一樣通過下標或索引直接訪問到資料
2. 新增元素
2.1 分析
新增元素分為三種情況,分別為在連結串列頭新增元素、在連結串列中間新增元素、在連結串列尾新增元素
-
在連結串列頭新增元素
- 只需要將新新增元素節點的指標指向頭節點,然後將頭節點設定新新增的元素
- 只需要將新新增元素節點的指標指向頭節點,然後將頭節點設定新新增的元素
-
在連結串列中間新增元素。假設待新增元素為節點A
- 向index位置新增元素時,需要先獲取前一個位置index-1節點,記為節點B,然後將節點A的next指標指向節點B的next指標,將節點B的next指標指向節點A。
eg: 必須先將節點A的next指標指向節點B的next指標,再將節點B的next指標指向節點A。順序不可以反過來,否則節點B的next指標指向節點A後,真正的index位置的節點就丟失了,無法根據節點B獲取到了
- 向index位置新增元素時,需要先獲取前一個位置index-1節點,記為節點B,然後將節點A的next指標指向節點B的next指標,將節點B的next指標指向節點A。
-
在連結串列尾新增元素
- 跟在連結串列中間新增元素一致。此時尾節點就是節點B,節點A的next指標指向節點B的next指標即為NULL,然後節點B的next指標指向新元素節點A
2.2 實現
public class LinkedList<E> { 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 head; private int size; public LinkedList() { this.head = null; this.size = 0; } // 獲取連結串列中的元素個數 public int getSize() { return this.size; } // 返回連結串列是否為空 public boolean isEmpty() { return this.size == 0; } // 在連結串列頭新增新的元素e public void addFirst(E e) { this.head = new Node(e, this.head); this.size++; } // 在連結串列的index(0-based)位置新增新元素e // 在連結串列中不是一個常用的操作,練習用 public void add(int index, E e) { if (index <0 || index > this.size) throw new IllegalArgumentException("Add failed. Illegal index."); if (index == 0) this.addFirst(e); else { Node prev = head; for (int i = 0; i < index - 1; i++) { prev = prev.next; } prev.next = new Node(e, prev.next); this.size++; } } // 在連結串列末尾新增新的元素e public void addLast(E e) { this.add(this.size, e); } }
3. 虛擬頭節點
3.1 分析
- 上面已經實現了在連結串列中新增元素,但在連結串列頭新增元素與其他位置新增元素業務邏輯有差別。此時可以通過虛擬頭節點解決該問題
- 虛擬頭節點不儲存任何資料
- 虛擬頭節點的next指標指向的節點才是真正的頭節點
3.2 實現
public class LinkedList<E> {
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() {
this.dummyHead = new Node(null, null);
this.size = 0;
}
// 獲取連結串列中的元素個數
public int getSize() {
return this.size;
}
// 返回連結串列是否為空
public boolean isEmpty() {
return this.size == 0;
}
// 在連結串列的index(0-based)位置新增新元素e
// 在連結串列中不是一個常用的操作,練習用
public void add(int index, E e) {
if (index <0 || index > this.size)
throw new IllegalArgumentException("Add failed. Illegal index.");
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
prev.next = new Node(e, prev.next);
this.size++;
}
// 在連結串列頭新增新的元素e
public void addFirst(E e) {
this.add(0, e);
}
// 在連結串列末尾新增新的元素e
public void addLast(E e) {
this.add(this.size, e);
}
}
4. 連結串列的遍歷、查詢、修改
// 獲得連結串列的第index(0-based)個位置的元素
// 在連結串列中不是一個常用的操作,練習用
public E get(int index) {
if (index < 0 || index > this.size)
throw new IllegalArgumentException("Get failed. Illegal index.");
Node cur = this.dummyHead.next;
for (int i = 0; i < index; i++)
cur = cur.next;
return cur.e;
}
// 獲得連結串列的第一個元素
public E getFirst() {
return this.get(0);
}
// 獲取連結串列的最後一個元素
public E getLast() {
return this.get(this.size - 1);
}
// 修改連結串列的第index(0-based)個位置的元素為e
// 在連結串列中不是一個常用的操作,練習用
public void set(int index, E e) {
if (index < 0 || index > this.size)
throw new IllegalArgumentException("Set failed. Illegal index.");
Node cur = this.dummyHead.next;
for (int i = 0; i < index; i++)
cur = cur.next;
cur.e = e;
}
// 查詢連結串列中是否存在元素e
public boolean contains(E e) {
Node cur = this.dummyHead.next;
while (cur != null) {
if (cur.e.equals(e))
return true;
cur = cur.next;
}
return false;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder("LinkedList: ");
Node cur = this.dummyHead.next;
while (cur != null) {
res.append(cur + " -> ");
cur = cur.next;
}
res.append("NULL");
return res.toString();
}
5. 從連結串列中刪除元素
// 從連結串列中刪除index(0-based)位置的元素,返回刪除的元素
// 從連結串列中刪除元素不是一個常用操作,練習用
public E remove(int index) {
if (index < 0 || index >= this.size)
throw new IllegalArgumentException("Remove failed. Index is illegal.");
Node prev = this.dummyHead;
for (int i = 0; i < index; i++)
prev = prev.next;
Node retNode = prev.next;
prev.next = retNode.next;
retNode.next = null;
this.size--;
return retNode.e;
}
// 從連結串列中刪除第一個元素,返回刪除的元素
public E removeFirst() {
return this.remove(0);
}
// 從連結串列中刪除最後一個元素,返回刪除的元素
public E removeLast() {
return this.remove(this.size - 1);
}