判断一个链表是否有环
这里分两种情况:
(1) O 形链表,这种就是常见的头尾相连的循环链表,即尾指针指向头指针
(2) 6 形链表,如下所示:
j←i←h←g
↓ ↑
a→b→c→d→e→f
一,对于第一种,直接遍历,如果最后指向头指针,则认为是 O 形循环链表,代码上很好处理,只需逐一遍历,并判断是否等于头结点即可:
bool list(sql *head )
{
if(!head) return false;
sql *q=head;
while(q->next)
{
if(q->next==head)
return true;
q=q->next;
}
return false;
}
二,对于第二种,6 形链表,判断是否有环,这里给出三种处理方法:
1)从第一个节点开始遍历,每遍历到一个新的节点,就再遍历一次之前遍历过的节点与当前新的节点进行比较,如果有与之相同的节点,说明之前已经遍历过这个节点,则形成了一个环,这里需要将遍历过的节点开辟一个新的存储空间,比如之前的例子:
j←i←h←g
↓ ↑
a→b→c→d→e→f
把这个环按照遍历的顺序横向展开:
a→b→c→d→e→f→g→h→i→j→c
从 a 开始遍历,当遍历到 j 节点时,还没形成环,此时之前遍历的节点都储存在新开辟的空间里,把 j 节点与之前的节点从头逐一比较并没有相同节点,同时将 j 储存起来,接着遍历下一节点 c 节点,把它与之前节点逐一比较,发现有与之相同的 c 节点,此时形成了环。
2)同样,从头遍历,每遍历一个新节点就到之前遍历过的节点中找有没有与之相同的节点,只不过这里用哈希表进行储存遍历过的节点,通过节点的键值进行映射,由于哈希表存储的是节点的地址,通过映射关系能直接查找是否存在相同的节点;这种方法和第一种处理方法相比,不用在在查找是否有相同节点这里费时,能够直接找,所以时间复杂度视为 O(1) ,效率上比第一种方法要好。
第二种方法在效率上已经最优,但是这两种方法都需要额外开辟空间,空间上不是最优,下面看第三种方法
3)设置快慢指针,fast, slow,开始都指向头指针,接着让 fast 的步长为 2,slow 的步长为 1,
show->next;
fast->next->next;
由于 fast 比 slow 快,如果有环,fast 必将能追上slow ,这里用个例子解释一下:
初中做过一种做过一种数学题叫 “追及问题”:
环形操场上,A 的速度为 V1 ,B 的速度为 v2 , 且 v2 > v1 ,问何时A与 B能相遇。
由于B 的速度比 A 的大,B定能在某一时刻“追上”A。
同样,由于 fast 比 slow 步长大,存在步长差,如果存在环,fast 和 slow 定能在某一时候相等 或者fast已经追上slow并且超越slow。反之如果不存在环,则可视为两人在一条直线上同向运动,由于步长差,后面 slow 一定不会等于 fast。
这里只附上第三种设置快慢指针的方法的代码,前两种需要可自行研究:
bool jduge(sql *head)
{
if(!head) return false;
sql* low=head;
sql* fast=head;
while(low->next||fast->next)
{
low=low->next;
fast=fast->next->next;
if(low==fast||low>fast)
return true;
}
return false;
}
第三种方法没有开辟新的空间,效率上只用了一个大循环,时间复杂度可视为O(n),空间上,效率上达到了最优。
并不能保证代码完全正确,只是写出了处理思路,如有错误之处,还请谅解,欢迎指出错误之处。