next 指向頭節點 (或虛擬頭節點)next 指向頭節點,頭節點 prev 指向尾節點curr.next == head 而非 curr == nullpackage list;
import java.util.Map;
public interface IListOps<T> {
boolean insertNode(T node);
boolean insertPosNode(int pos, T node);
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;
// getter/setter/toString 同前
}
package list;
import java.util.HashMap;
import java.util.Map;
public class SinglyCircularList implements IListOps<SNode> {
private final SNode head = new SNode("HEAD");
private int size = 0;
@Override public int getSize() { return size; }
/** 初始化:頭節點指向自己 */
private void init() {
if (size == 0) head.setNext(head);
}
/** 尾部插入:找到尾節點 (next == head) */
@Override
public boolean insertNode(SNode node) {
init();
if (size == 0) {
head.setNext(node);
node.setNext(head); // 關鍵:指向頭節點形成循環
} else {
SNode curr = head;
while (curr.getNext() != head) curr = curr.getNext(); // 找尾節點
curr.setNext(node);
node.setNext(head);
}
size++;
return true;
}
/** 指定位置插入 (1-based) */
@Override
public boolean insertPosNode(int pos, SNode node) {
init();
if (pos <= 0 || pos > size + 1) return false;
if (size == 0) { // 空串列
head.setNext(node);
node.setNext(head);
size++;
return true;
}
if (pos == size + 1) return insertNode(node); // 尾插
// 找到第 pos-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;
// 找到第 pos-1 個節點 (前驅)
SNode prev = head;
for (int i = 1; i < pos; i++) prev = prev.getNext();
// 刪除 prev.next
prev.setNext(prev.getNext().getNext());
size--;
System.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;
}
@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 + ") ===");
if (size == 0) { System.out.println("空串列"); return; }
SNode curr = head.getNext();
int idx = 1;
do {
System.out.println("第 " + idx++ + " 節點: " + curr);
curr = curr.getNext();
} while (curr != head); // 關鍵:直到回到頭節點
}
public static void main(String[] args) {
SinglyCircularList list = new SinglyCircularList();
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();
}
}
package list;
public class DNode {
private DNode prev;
private String data;
private DNode next;
// getter/setter/toString 同前
}
package list;
import java.util.HashMap;
import java.util.Map;
public class DoublyCircularList implements IListOps<DNode> {
private final DNode head = new DNode("HEAD");
private int size = 0;
@Override public int getSize() { return size; }
/** 初始化:頭節點 prev/next 都指向自己 */
private void init() {
if (size == 0) {
head.setNext(head);
head.setPrev(head);
}
}
@Override
public boolean insertNode(DNode node) {
init();
if (size == 0) {
head.setNext(node);
head.setPrev(node);
node.setNext(head);
node.setPrev(head);
} else {
DNode tail = head.getPrev(); // 尾節點 = head.prev
tail.setNext(node);
node.setPrev(tail);
node.setNext(head);
head.setPrev(node);
}
size++;
return true;
}
@Override
public boolean insertPosNode(int pos, DNode node) {
init();
if (pos <= 0 || pos > size + 1) return false;
if (size == 0) {
head.setNext(node); head.setPrev(node);
node.setNext(head); node.setPrev(head);
size++; return true;
}
if (pos == size + 1) return insertNode(node);
// 找到第 pos 個節點 (作為後繼節點)
DNode curr = head.getNext();
for (int i = 1; i < pos; i++) curr = curr.getNext();
// 插入到 curr 之前
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);
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 + ") ===");
if (size == 0) { System.out.println("空串列"); return; }
DNode curr = head.getNext();
int idx = 1;
do {
System.out.println("第 " + idx++ + " 節點: " + curr);
curr = curr.getNext();
} while (curr != head);
}
public static void main(String[] args) {
DoublyCircularList list = new DoublyCircularList();
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(2);
list.printList();
}
}
| 特性 | 單向循環 | 雙向循環 |
|---|---|---|
| 頭節點初始化 | head.next = head | head.next = head.prev = head |
| 尾節點判斷 | curr.next == head | curr.next == head (或 head.prev == tail) |
| 尾部插入 | 遍歷找尾 O(n) | head.getPrev() 直接得尾 O(1) |
| 插入指標更新 | 2 個 next | 4 個指標 (prev/next 對稱) |
| 刪除指標更新 | 1 個 next | 2 個指標 (prev.next, next.prev) |
| 反向遍歷 | 不可 | 可 (curr = curr.prev) |
| Josephus 問題 | 適用 (單向計數) | 適用 (雙向可雙向計數) |
/**
* N 個人圍圈,從第 1 個報數,報到 M 的人出列,繼續報數,求最後剩下誰
* 單向循環鏈結串列完美模擬
*/
public class Josephus {
public static int solve(int n, int m) {
SinglyCircularList list = new SinglyCircularList();
for (int i = 1; i <= n; i++) list.insertNode(new SNode(String.valueOf(i)));
SNode curr = list.getNode(1, null); // 從第1個開始
while (list.getSize() > 1) {
// 移動 m-1 步 (當前已算1)
for (int i = 1; i < m; i++) curr = curr.getNext();
// 刪除當前節點:需要前驅節點
// 單向循環無直接前驅,需從 head 找或維護 prev
// 此處簡化:轉用索引刪除
int pos = 1;
SNode temp = list.getNode(1, null);
while (temp != curr) { temp = temp.getNext(); pos++; }
list.deleteNode(pos);
// 下一輪從被刪節點的下一個開始
curr = curr.getNext();
}
return Integer.parseInt(list.getNode(1, null).getData());
}
public static void main(String[] args) {
System.out.println("Josephus(5,3) = " + solve(5, 3)); // 輸出 4
System.out.println("Josephus(10,2) = " + solve(10, 2)); // 輸出 5
}
}
public class MusicPlayer {
private DoublyCircularList playlist = new DoublyCircularList();
private DNode current = null;
public void addSong(String name) {
playlist.insertNode(new DNode(name));
if (current == null) current = playlist.getNode(1, null);
}
public void playCurrent() {
if (current != null) System.out.println("♪ 正在播放: " + current.getData());
}
public void next() {
if (current != null) {
current = current.getNext();
if (current.getData().equals("HEAD")) current = current.getNext(); // 跳過頭節點
playCurrent();
}
}
public void prev() {
if (current != null) {
current = current.getPrev();
if (current.getData().equals("HEAD")) current = current.getPrev();
playCurrent();
}
}
public static void main(String[] args) {
MusicPlayer player = new MusicPlayer();
player.addSong("歌曲A");
player.addSong("歌曲B");
player.addSong("歌曲C");
player.playCurrent(); // ♪ 歌曲A
player.next(); // ♪ 歌曲B
player.next(); // ♪ 歌曲C
player.next(); // ♪ 歌曲A (循環)
player.prev(); // ♪ 歌曲C (倒轉)
}
}
curr != null 而非 curr != head)size == 0 直接操作指標/** 驗證循環完整性 */
public boolean validateCircular() {
if (size == 0) return head.getNext() == head;
int count = 0;
Node curr = head.getNext();
do {
count++;
if (count > size * 2) return false; // 防呆
curr = curr.getNext();
} while (curr != head);
return count == size;
}
/** 列印指標結構 (除錯用) */
public void debugPrint() {
Node curr = head;
do {
String prevData = curr.getPrev() != null ? curr.getPrev().getData() : "null";
String nextData = curr.getNext() != null ? curr.getNext().getData() : "null";
System.out.println("[" + curr.getData() + "] prev=" + prevData + ", next=" + nextData);
curr = curr.getNext();
} while (curr != head);
}
null 邊界,簡化循環邏輯head.next == head (空) 或 head.prev.next == head (非空) 必須始終成立掌握循環鏈結串列是理解作業系統排程、網路協議環形緩衝、遊戲邏輯循環等核心基礎,建議多手寫、多畫圖驗證指標變化。