判断单链表是否存在环及寻找环的入口点

本文详细介绍了链表环检测的三种方法:使用哈希表、反转链表和快慢指针,并提供了对应的代码实现。重点讨论了如何通过反转链表来判断链表是否存在环,以及如何利用快慢指针找到环的入口点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

判断链表是否存在环,有如下几种解法:
1. 遍历链表,将已经遍历过的节点放在一个hash表中,如果一个节点已经存在hash表中,说明有环。时间:O(n) 空间:O(n)
2. 反转链表。 时间O(n),空间O(1),使用三个指针。(ref:  http://www.cppblog.com/tx7do/archive/2009/01/06/71280.html)

    单链表反转:下面给出两种可能的实现。

    普通版:

  1. void reverse(node*& head)  
  2. {  
  3.     if ( (head == 0) || (head->next == 0) ) return;// 边界检测  
  4.     node* pNext = 0;  
  5.     node* pPrev = head;// 保存链表头节点  
  6.     node* pCur = head->next;// 获取当前节点  
  7.     while (pCur != 0)  
  8.     {  
  9.         pNext = pCur->next;// 将下一个节点保存下来  
  10.         pCur->next = pPrev;// 将当前节点的下一节点置为前节点  
  11.         pPrev = pCur;// 将当前节点保存为前一节点  
  12.         pCur = pNext;// 将当前节点置为下一节点  
  13.     }  
  14.     head->next = NULL;  
  15.     head = pPrev;  
  16. p;}  
    递归版:

  1. node* reverse( node* pNode, node*& head)  
  2. {  
  3.     if ( (pNode == 0) || (pNode->next == 0) ) // 递归跳出条件  
  4.     {  
  5.         head = pNode; // 将链表切断,否则会形成回环  
  6.         return pNode;  
  7.     }  
  8.   
  9.     node* temp = reserve(pNode->next, head);// 递归  
  10.     temp->next = pNode;// 将下一节点置为当前节点,既前置节点  
  11.     return pNode;// 返回当前节点  
  12. }  

      使用反转链表的方法, 每过一个节点就把该节点的指针反向。若存在环,反转next指针最终会走到链表头部。若没有环,反转next指针会破坏链表结构(使链表反向), 所以为还原链表,需要把链表再反向一次。 这种方法的空间复杂度是O(1), 实事上我们使用了3个额外指针;而时间复杂度是O(n), 我们最多2次遍历整个链表(当链表中没有环的时候)。下面给出一个实现,但最大的问题是:若存在环,则无法还原到链表的原状态

  1. bool reverse(Node *head) {  
  2.  Node *curr = head;  
  3.  Node *next = head->next;  
  4.  curr->next = NULL;  
  5.   
  6.  while(next!=NULL) {  
  7.   if(next == head) { /* go back to the head of the list, so there is a loop */  
  8.     next->next = curr;  
  9.     return true;  
  10.   }  
  11.   
  12.   Node *temp = curr;  
  13.   curr = next;  
  14.   next = next->next;  
  15.   curr->next = temp;  
  16.  }  
  17.   
  18.  /* at the end of list, so there is no loop, let's reverse the list back */  
  19.  next = curr->next;  
  20.  curr ->next = NULL;  
  21.  while(next!=NULL) {  
  22.   Node *temp = curr;  
  23.   curr = next;  
  24.   next = next->next;  
  25.   curr->next = temp;  
  26.  }  
  27.   
  28.  return false;  
  29. }  



3. 快慢指针。 时间O(n),空间O(1),使用两个指针。(ref: http://blog.youkuaiyun.com/mingming_bupt/article/details/6331333)

    判断环的存在:设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步。如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则是无环链表)。

  1. bool IsExitsLoop(slist * head)  
  2. {  
  3. slist * slow = head , * fast = head;  
  4.   
  5. while ( fast && fast -> next )  
  6. {  
  7. slow = slow -> next;  
  8. fast = fast -> next -> next;  
  9. if ( slow == fast ) break ;  
  10. }  
  11.   
  12. return ! (fast == NULL || fast -> next == NULL);  
  13. }   
     寻找环的入口点 : 当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再次重合的时候,必然是在链表的环路入口点上。

  1. slist * FindLoopPort(slist * head)  
  2. {  
  3. slist * slow = head, * fast = head;  
  4.   
  5. while ( fast && fast -> next )  
  6. {  
  7. slow = slow -> next;  
  8. fast = fast -> next -> next;  
  9. if ( slow == fast ) break ;  
  10. }  
  11.   
  12. if (fast == NULL || fast -> next == NULL)  
  13. return NULL;  
  14.   
  15. slow = head;  
  16. while (slow != fast)  
  17. {  
  18. slow = slow -> next;  
  19. fast = fast -> next;  
  20. }  
  21.   
  22. return slow;  
  23. }  

转载:http://blog.youkuaiyun.com/xudacheng06/article/details/7706245
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值