步骤一:判断链表是否有环,如果有,则返回相遇的节点,如果没有则返回NULL
算法:通过设置快慢指针来判断相遇,快指针一次走两个节点,慢指针一次走一个,若两个指针能相遇则说明有环,返回相遇的节点,若快指针走到NULL都没发生相遇,则说明没环,返回NULL
C语言代码:
LinkList Loop(LinkList plist)
{
if (plist == NULL || plist->next == NULL)
{
return NULL;
}
LinkList p = plist; // 快指针
LinkList q = plist; // 慢指针
while (p != NULL && p->next != NULL) // p->next != NULL 保证快指针一次走两个节点能够成功
{
p = p->next->next;
q = q->next;
if (p == q)
{
return p;
}
}
return NULL;
}
步骤二:找到有环链表的环的入口
算法一:通过数学推导来找入口,具体如下:
设置快慢指针,快指针一次走两个节点,慢指针一次走一个节点,如下图,plist为链表起点,m为环入口,s点为快慢指针相遇点。
当快慢指针相遇在s点,慢指针走过的路程为 x + y ,快指针走过的路程为x + y + (k +y) * n,n是快指针绕环的圈数。
因为快指针速度是慢指针二倍,快慢指针移动的时间相同,所以快指针的路程等于慢指针路程的二倍,即2(x + y) = x + y + (k + y)n
化解:x + y = (k + y)n
x + y = (k + y)(n - 1) + k+y
x = (k + y)(n-1) + k
该式里,右边(k + y)(n -1)是绕环的路程,即从plist起点开始到环入口的路程等于从相遇点s开始到m的路程加上绕环的路程
因而可以让两个速度相同的指针,一个从plist开始走,一个从s点开始走,二者第一次相遇的点就是环入口m点
C代码如下:
LinkList IsLoop(LinkList plist)
{
LinkList s = Loop(plist);//通过上一步骤的Loop函数找到快慢指针相遇点
if (s == NULL)
{
return NULL;
}
LinkList p = plist;
LinkList q = s;
while (p != q)
{
p = p->next;
q = q->next;
}
return p;//p就是入环的第一个节点
}
算法二:遍历节点时把节点存储在一个表里,每次移动指针都在表里查看有无当前节点,若有出现重复则返回该节点,若无则继续遍历。该算法思路较为简单,但效率不高,O(n^2)
C代码:
#define TABLESIZE 10
Linklist FindCircleStart(Linklist plist)//如果有环,找到入幻的第一个节点
{
assert(plist != NULL);
if (plist == NULL) return NULL;
Linklist p = plist;
if (IsCircle(p))
{
//遍历p,把节点存起来,第一个出现重复的节点就是环的入口
Linklist table[TABLESIZE];
int i = 0;
while (p)
{
table[i] = p;
p = p->next;
i++;
if (i > TABLESIZE)
{
realloc(table, sizeof(table) * 2);
}
for (int j = 0; j < i; j++)//看已存的表里有没有重复的
{
for (int k = j +1; k < i; k++)
{
if (table[k] == table[j])
return table[k];
}
}
}
return NULL;
}
else
return NULL;
}