一、判断单链表是否存在环
这个问题有很多方法,最容易想到的就是记录每个节点记录的次数。这里也介绍的是另一种简单而常见的方法
快慢指针法:
定义两个指针slow, fast。slow指针一次走1个结点,fast指针一次走2个结点。如果链表中有环,那么慢指针一定会再某一个时刻追上快指针(slow == fast)。如果没有环,则快指针会第一个走到NULL
1 int has_cycle(node *head) { 2 if (head == NULL) return false; 3 node* fast = head; 4 node* slow = head; 5 6 while (1) 7 { 8 if (slow->next != NULL) slow = slow->next; //慢指针走一步 9 else return false; 10 if (fast->next != NULL && fast->next->next != NULL) fast = fast->next->next; //快指针走两步 11 else return false; 12 13 if (slow == fast) return true; 14 } 15 }
二、寻找环的入口点
其中一个回到起点:
当fast按照每次2步,slow每次一步的方式走,发现fast和slow重合,确定了单向链表有环路。接下来,让fast回到链表的头部,重新走,每次步长1,那么当fast和slow再次相遇的时候,就是环路的入口了。
证明:
在fast和slow第一次相遇的时候,假定slow走了n步,环路的入口是在p步,那么
slow走的路径: p+c = n;(1) c为fast和slow相交点 距离环路入口的距离
fast走的路径: p+c+k*L = 2*n;(2), L为环路的周长,k是整数
fast从头开始走,步长为1.
经过n步,fast和slow都会到达p + c这一点。将(2)-(1)得k*L = n,说明n是L的倍数,同时p + c = n,
所以fast和slow都走p步时,fast距(p + c)差c,slow还差c回到(p + c),所以p是他们的第一个交点,之后的轨迹就一模一样了。
1 node* find_loopport(node * head) 2 { 3 node* fast = head; 4 node* slow = head; 5 6 //两个互指不考虑 7 //判断是否存在环,如果存在得到相遇位置 8 while (fast && fast->next) 9 { 10 slow = slow->next; 11 fast = fast->next->next; 12 if (fast == slow) break; 13 } 14 //fast到达NULL,表示不存在环 15 if (fast == NULL || fast->next == NULL) return NULL; 16 17 //将一个指针移到开始处,步长都变成一 18 fast = head; 19 while (slow != fast) 20 { 21 slow = slow->next; 22 fast = fast->next; 23 } 24 return slow; 25 }