判断一个单链表是否带环,我们可以设置两个指针,一快一慢,慢的指针每次走一步,快的指针每次走两步,如果在遍历过程中,快慢指针相遇,则表明链表有环,否则无环;而,如何找到环的入口点呢?
给出一个定理:快慢指针的相遇点到环入口点的距离等于头结点到环入口点的距离;
LinkList Node {
int value;
LinkList next;
};
LinkList findCircleNode(LinkList *head) {
LinkList *slow = head;
LinkList *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;
}
return slow;
}
假设链表在(甩尾)环外长度为 a(结点个数),环内长度为 b 。
则总长度(也是总结点数)为 a+b 。
从头开始,0 base 编号。
将第 i 步访问的结点用 S(i) 表示。i = 0, 1 …
当 i<a 时,S(i)=i ;
当 i≥a 时,S(i)=a+(i-a)%b 。
分析追赶过程:
两个指针分别前进,假定经过 x 步后,相遇。则有:S(x)=S(2x)
由环的周期性有:2x=tb+x 。得到 x=tb 。
另外,相遇时,必须在环内,不可能在甩尾段,有 x>=a 。
连接点为从起点走 a 步,即 S(a)。
S(a) = S(tb+a) = S(x+a)。
得到结论:从碰撞点 x 前进 a 步即为连接点。
根据假设易知 S(a-1) 在甩尾段,S(a) 在环上,而 S(x+a) 必然在环上。所以可以发生碰撞。
而,同为前进 a 步,同为连接点,所以必然发生碰撞。
综上,从 x 点和从起点同步前进,第一个碰撞点就是连接点。
/
假设单链表的总长度为L,头结点到环入口的距离为a,环入口到快慢指针相遇的结点距离为x,环的长度为r,慢指针总共走了s步,则快指针走了2s步。另外,快指针要追上慢指针的话快指针至少要在环里面转了一圈多(假设转了n圈加x的距离),得到以下关系:
s = a + x;
2s = a + nr + x;
=>a + x = nr;
=>a = nr - x;
由上式可知:若在头结点和相遇结点分别设一指针,同步(单步)前进,则最后一定相遇在环入口节点;