判断链表是否存在环,有如下几种解法:
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;
- p;}
- 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.youkuaiyun.com/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和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;
- }
-