單向鏈表若存在環(某節點 next 指向前序節點),普通遍歷會陷入無限迴圈。本文詳細解析 **Floyd 判圈演算法(快慢指針)** 的數學原理、代碼實作、環入口定位邏輯,並延伸至無環雙鏈表相交判斷。
bool has_cycle(ListNode* head) {
ListNode *slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next; // 1 步
fast = fast->next->next; // 2 步
if (slow == fast) return true; // 相遇 → 有環
}
return false; // fast 到達 NULL → 無環
}
L,慢指針進環時快指針已在環內領先 k 步L 輪追上fast 先到達 NULL 終止
ListNode* find_cycle_entry(ListNode* head) {
ListNode *slow = head, *fast = head;
// 第一階段:判斷有環並獲取相遇點
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) break;
}
if (!fast || !fast->next) return NULL; // 無環
// 第二階段:slow 歸位 head,雙指針同速前進
slow = head;
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow; // 相遇點即環入口
}
設:
a:頭節點到環入口距離b:環入口到相遇點距離L:環長相遇時:slow 走 a+b,fast 走 a+b+nL (n≥1)
因 fast = 2 × slow → a+b+nL = 2(a+b) → a = nL - b = (n-1)L + (L-b)
L-b 即相遇點到入口距離。故從頭出發走 a 步,與從相遇點出發走 a 步,必在入口相遇。
next = NULL
ListNode* get_intersection(ListNode* headA, ListNode* headB) {
// 1. 遍歷得長度與尾節點
int lenA=0, lenB=0;
ListNode *pA=headA, *pB=headB, *tailA=NULL, *tailB=NULL;
while (pA) { ++lenA; tailA=pA; pA=pA->next; }
while (pB) { ++lenB; tailB=pB; pB=pB->next; }
if (tailA != tailB) return NULL; // 尾不同 → 不相交
// 2. 長鏈表先走差值步
pA = headA; pB = headB;
if (lenA > lenB) for (int i=0; inext;
else for (int i=0; inext;
// 3. 同步前進直到相遇
while (pA != pB) { pA = pA->next; pB = pB->next; }
return pA;
}
| 問題 | 時間 | 空間 | 關鍵技巧 |
|---|---|---|---|
| 判圈 | O(N) | O(1) | 快慢指針相對速度 |
| 找入口 | O(N) | O(1) | a = nL - b 數學關係 |
| 雙鏈表相交 | O(N) | O(1) | 尾節點同一性 + 長度差對齊 |
Floyd 演算法以極簡代碼解決看似需要額外空間的問題,是指標演算法中的經典典範。理解其數學本質,有助於舉一反三解決更多鏈表、圖論中的週期檢測問題。