判斷鏈表是否存在環,有如下幾種解法:
1. 遍曆鏈表,將已經遍曆過的節點放在一個hash表中,如果一個節點已經存在hash表中,說明有環。時間:O(n) 空間:O(n)
2. 反轉鏈表。 時間O(n),空間O(1),使用三個指標。(ref: http://www.cppblog.com/tx7do/archive/2009/01/06/71280.html)
單鏈表反轉:下面給出兩種可能的實現。
普通版:
void reverse(node*& head) { if ( (head == 0) || (head->next == 0) ) return;// 邊界檢測 node* pNext = 0; node* pPrev = head;// 儲存鏈表前端節點 node* pCur = head->next;// 擷取當前節點 while (pCur != 0) { pNext = pCur->next;// 將下一個節點儲存下來 pCur->next = pPrev;// 將當前節點的下一節點置為前節點 pPrev = pCur;// 將當前節點儲存為前一節點 pCur = pNext;// 將當前節點置為下一節點 } head->next = NULL; head = pPrev; }
遞迴版:
node* reverse( node* pNode, node*& head) { if ( (pNode == 0) || (pNode->next == 0) ) // 遞迴跳出條件 { head = pNode; // 將鏈表切斷,否則會形成迴環 return pNode; } node* temp = reserve(pNode->next, head);// 遞迴 temp->next = pNode;// 將下一節點置為當前節點,既前置節點 return pNode;// 返回當前節點 }
使用反轉鏈表的方法, 每過一個節點就把該節點的指標反向。若存在環,反轉next指標最終會走到鏈表頭部。若沒有環,反轉next指標會破壞鏈表結構(使鏈表反向), 所以為還原鏈表,需要把鏈表再反向一次。 這種方法的空間複雜度是O(1), 實事上我們使用了3個額外指標;而時間複雜度是O(n), 我們最多2次遍曆整個鏈表(當鏈表中沒有環的時候)。下面給出一個實現,但最大的問題是:若存在環,則無法還原到鏈表的原狀態。
bool reverse(Node *head) { Node *curr = head; Node *next = head->next; curr->next = NULL; while(next!=NULL) { if(next == head) { /* go back to the head of the list, so there is a loop */ next->next = curr; return true; } Node *temp = curr; curr = next; next = next->next; curr->next = temp; } /* at the end of list, so there is no loop, let's reverse the list back */ next = curr->next; curr ->next = NULL; while(next!=NULL) { Node *temp = curr; curr = next; next = next->next; curr->next = temp; } return false; }
3. 快慢指標。 時間O(n),空間O(1),使用兩個指標。(ref: http://blog.csdn.net/mingming_bupt/article/details/6331333)
判斷環的存在:設定兩個指標(fast, slow),初始值都指向頭,slow每次前進一步,fast每次前進二步。如果鏈表存在環,則fast必定先進入環,而slow後進入環,兩個指標必定相遇。(當然,fast先行頭到尾部為NULL,則是無環鏈表)。
bool IsExitsLoop(slist * head){slist * slow = head , * fast = head;while ( fast && fast -> next ){slow = slow -> next;fast = fast -> next -> next;if ( slow == fast ) break ;}return ! (fast == NULL || fast -> next == NULL);}
尋找環的進入點: 當fast按照每次2步,slow每次一步的方式走,發現fast和slow重合,確定了單向鏈表有環路。接下來,讓fast回到鏈表的頭部,重新走,每次步長1,那麼當fast和slow再次相遇的時候,就是環路的入口了。
證明:在fast和slow第一次相遇的時候,假定slow走了n步,環路的入口是在p步,那麼
slow走的路徑: p+c = n; c為fast和slow相交點 距離環路入口的距離
fast走的路徑: p+c+k*L = 2*n; L為環路的周長,k是整數
顯然,如果從p+c點開始,slow再走n步的話,還可以回到p+c這個點。
同時,fast從頭開始走,步長為1,經過n步,也會達到p+c這點。
顯然,在這個過程中fast和slow只有前p步驟走的路徑不同。所以當p1和p2再次重合的時候,必然是在鏈表的環路進入點上。
slist * FindLoopPort(slist * head){slist * slow = head, * fast = head;while ( fast && fast -> next ){slow = slow -> next;fast = fast -> next -> next;if ( slow == fast ) break ;}if (fast == NULL || fast -> next == NULL)return NULL;slow = head;while (slow != fast){slow = slow -> next;fast = fast -> next;}return slow;}