鏈結串列 由節點 組成,每個節點包含資料域與指標域。依指標方向分為:
next 指向後繼節點prev (前驅) 與 next (後繼)package list;
import java.util.Map;
public interface IListOps<T> {
boolean insertNode(T node); // 尾部插入
boolean insertPosNode(int pos, T node); // 指定位置插入 (1-based)
boolean deleteNode(int pos); // 指定位置刪除
boolean updateNode(int pos, Map<String, Object> data); // 更新節點資料
T getNode(int pos, Map<String, Object> data); // 取得節點
void printList(); // 列印整個串列
int getSize(); // 取得長度
}
package list;
public class SNode {
private String data;
private SNode next;
public SNode() {}
public SNode(String data) { this.data = data; }
public String getData() { return data; }
public void setData(String data) { this.data = data; }
public SNode getNext() { return next; }
public void setNext(SNode next) { this.next = next; }
@Override
public String toString() { return "SNode[data=" + data + "]"; }
}
package list;
import java.util.HashMap;
import java.util.Map;
public class SinglyLinkedList implements IListOps<SNode> {
private final SNode head = new SNode("HEAD"); // 虛擬頭節點,不存資料
private int size = 0;
@Override
public int getSize() { return size; }
/** 尾部插入 O(n) - 若維護 tail 指標可優化為 O(1) */
@Override
public boolean insertNode(SNode node) {
SNode curr = head;
while (curr.getNext() != null) curr = curr.getNext();
curr.setNext(node);
node.setNext(null);
size++;
return true;
}
/** 指定位置插入 (1-based) */
@Override
public boolean insertPosNode(int pos, SNode node) {
if (pos <= 0 || pos > size + 1) return false; // 允許 pos = size+1 (尾插)
SNode prev = head;
for (int i = 1; i < pos; i++) prev = prev.getNext();
node.setNext(prev.getNext());
prev.setNext(node);
size++;
return true;
}
/** 指定位置刪除 */
@Override
public boolean deleteNode(int pos) {
if (pos <= 0 || pos > size) return false;
SNode prev = head;
for (int i = 1; i < pos; i++) prev = prev.getNext();
prev.setNext(prev.getNext().getNext()); // 跳過被刪節點
size--;
System.gc(); // 提示 GC 回收 (非強制)
return true;
}
/** 更新節點資料 */
@Override
public boolean updateNode(int pos, Map<String, Object> data) {
SNode node = getNode(pos, null);
if (node != null && data != null) {
node.setData((String) data.get("data"));
return true;
}
return false;
}
/** 取得節點 (1-based) */
@Override
public SNode getNode(int pos, Map<String, Object> data) {
if (pos <= 0 || pos > size) return null;
SNode curr = head.getNext();
for (int i = 1; i < pos; i++) curr = curr.getNext();
return curr;
}
/** 列印串列 */
@Override
public void printList() {
System.out.println("=== 單向鏈結串列 (長度: " + size + ") ===");
SNode curr = head.getNext();
int idx = 1;
while (curr != null) {
System.out.println("第 " + idx++ + " 節點: " + curr);
curr = curr.getNext();
}
}
// 測試主程式
public static void main(String[] args) {
SinglyLinkedList list = new SinglyLinkedList();
list.insertNode(new SNode("節點1"));
list.insertNode(new SNode("節點2"));
list.insertNode(new SNode("節點3"));
list.insertPosNode(2, new SNode("插入節點"));
list.printList();
list.deleteNode(2);
list.printList();
list.updateNode(1, Map.of("data", "已修改"));
list.printList();
}
}
package list;
public class DNode {
private DNode prev;
private String data;
private DNode next;
public DNode() {}
public DNode(String data) { this.data = data; }
public DNode getPrev() { return prev; }
public void setPrev(DNode prev) { this.prev = prev; }
public String getData() { return data; }
public void setData(String data) { this.data = data; }
public DNode getNext() { return next; }
public void setNext(DNode next) { this.next = next; }
@Override
public String toString() { return "DNode[data=" + data + "]"; }
}
package list;
import java.util.HashMap;
import java.util.Map;
public class DoublyLinkedList implements IListOps<DNode> {
private final DNode head = new DNode("HEAD");
private int size = 0;
@Override
public int getSize() { return size; }
@Override
public boolean insertNode(DNode node) {
DNode curr = head;
while (curr.getNext() != null) curr = curr.getNext();
curr.setNext(node);
node.setPrev(curr);
node.setNext(null);
size++;
return true;
}
@Override
public boolean insertPosNode(int pos, DNode node) {
if (pos <= 0 || pos > size + 1) return false;
DNode curr = head.getNext();
// 找到插入位置的「後繼節點」 (若 pos=size+1 則 curr=null)
for (int i = 1; i < pos && curr != null; i++) curr = curr.getNext();
if (curr == null) { // 尾插
DNode tail = head;
while (tail.getNext() != null) tail = tail.getNext();
tail.setNext(node);
node.setPrev(tail);
node.setNext(null);
} else { // 中間插入
DNode prev = curr.getPrev();
prev.setNext(node);
node.setPrev(prev);
node.setNext(curr);
curr.setPrev(node);
}
size++;
return true;
}
@Override
public boolean deleteNode(int pos) {
if (pos <= 0 || pos > size) return false;
DNode curr = head.getNext();
for (int i = 1; i < pos; i++) curr = curr.getNext();
DNode prev = curr.getPrev();
DNode next = curr.getNext();
prev.setNext(next);
if (next != null) next.setPrev(prev); // 非尾節點才需設定
size--;
System.gc();
return true;
}
@Override
public boolean updateNode(int pos, Map<String, Object> data) {
DNode node = getNode(pos, null);
if (node != null && data != null) {
node.setData((String) data.get("data"));
return true;
}
return false;
}
@Override
public DNode getNode(int pos, Map<String, Object> data) {
if (pos <= 0 || pos > size) return null;
DNode curr = head.getNext();
for (int i = 1; i < pos; i++) curr = curr.getNext();
return curr;
}
@Override
public void printList() {
System.out.println("=== 雙向鏈結串列 (長度: " + size + ") ===");
DNode curr = head.getNext();
int idx = 1;
while (curr != null) {
System.out.println("第 " + idx++ + " 節點: " + curr);
curr = curr.getNext();
}
}
public static void main(String[] args) {
DoublyLinkedList list = new DoublyLinkedList();
list.insertPosNode(1, new DNode("節點1"));
list.insertPosNode(2, new DNode("節點2"));
list.insertPosNode(3, new DNode("節點3"));
list.insertPosNode(2, new DNode("插入節點"));
list.printList();
list.deleteNode(4);
list.printList();
}
}
| 操作 | 單向鏈結串列 | 雙向鏈結串列 | 陣列 |
|---|---|---|---|
| 尾部插入 | O(n) / O(1)* | O(n) / O(1)* | O(1) 擴容 |
| 頭部插入 | O(1) | O(1) | O(n) |
| 指定位置插入 | O(n) | O(n) | O(n) |
| 指定位置刪除 | O(n) | O(n) | O(n) |
| 指定位置查詢 | O(n) | O(n) | O(1) |
| 反向遍歷 | 不可 / O(n) | O(n) | O(1) |
| 記憶體開銷 | 1 指標/節點 | 2 指標/節點 | 連續記憶體 |
* 維護 tail 指標時
// java.util.LinkedList - 雙向鏈結串列 (實作 List + Deque)
LinkedList<String> list = new LinkedList<>();
list.add("A"); // 尾部插入
list.addFirst("B"); // 頭部插入
list.addLast("C"); // 尾部插入
list.add(1, "D"); // 指定位置插入
list.removeFirst(); // 移除頭部
list.get(0); // 索引存取 (O(n))
list.listIterator(2); // 從指定位置雙向迭代
// 適用場景:頻繁頭尾操作、佇列/棧、迭代器雙向遍歷
// 不適用:大量隨機存取 (用 ArrayList)
head.next == null 直接存取prevpos > size vs pos >= sizeCollections.synchronizedList 或 ConcurrentLinkedQueue)private Node tail = head;
public boolean insertNode(Node node) {
tail.setNext(node);
tail = node;
size++;
return true;
}
// 找中點
Node slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// slow 即中點 (偶數長度取偏前/偏後視條件而定)
本文所有實作均採用 head = new Node("HEAD"),使頭部操作與中間操作邏輯一致,避免大量 if (head == null) 判斷。
java.util.LinkedList 或 ArrayList,手寫鏈結串列僅用於面試、教學、特殊演算法 (LRU、跳表、並發結構)掌握指標操作邏輯 (先連後斷、prev/next 對稱更新) 是鏈結串列實作的核心,多畫圖推演可避免邏輯錯誤。