一个单向数据链表,如果有形成环,如何判断是否有环,且环的启示位置在哪呢?
笨拙的方法就是申请一个很大的指针数据,用来存储已经遇到的指针地址,并且用来判断是否有重复,不过仔细想想得动态申请多大一块呢?oh, god!真的是很笨的方法。
下面说一个比较有效的方法,算法复杂度不高(O(n))、空间复杂度(O(1))也很低的方法:
跑步
在理解这个算法之前,先和大家讨论一个关于跑步的问题,大家应该都知道:
两个人在环形操场跑步,一个比较快,一个跑的慢一点。同时出发,当两者相遇的时候,一定是快者比慢者多跑了一圈了。
有了这个概念,我们接下来讨论下,快慢指针。
快慢指针
下面我们继续讨论快慢指针:
顾名思义,一个指针跑的快,一个指针跑的慢。两个指针,一个每次移动两位记为pf2, 一个每次移动一位记为pf1.
当pf1 总共走了x 部之后,pf1 、pf2相遇了。环之前的长度为s, 相遇点P。
由图可以得到此时的关系
总长度:M;
环长度: N;
pf1 路程: x = s + N - L;
pf2 路程: 2x = s + rN + N - L ;
因为不确定N 到底有多大,所以不能确定在相遇之前,pf2 已经绕了几圈了。
update:
s + rN + N - L = 2 * (s + N -L)
⇒ s = rN + L
结合该公式和图,大家应该可以明白,当两个人速度一样,一个从起始点出发,一个从P点出发,最终的相遇点,一定是环的入口处。
所以我们的程序就知道该如何写了,下面是小弟所写的代码,供大家参考。
创建有环单链表
static int create_link(linklist *header)
{
int count = 0;
linklist * pf1 = header;
linklist * pf = NULL;
do
{
linklist * pf2 = (linklist *)malloc(sizeof(linklist));
if (pf2 == NULL)
return 1;
pf1->next = pf2;
pf1 = pf2;
count ++;
if (count == 6)
pf = pf2;
} while(count < 14);
pf1->next = pf;
return 0;
}
查找环入口
static int find_cycle(linklist *header)
{
linklist *pf2 = header;
linklist *pf1 = header;
int count = 0;
do {
if (pf2->next == NULL)
return -1;
else if (pf2->next == NULL)
return -1;
else {
pf2 = pf2->next->next;
pf1 = pf1->next;
}
if (pf1 == pf2)
break;
} while(true);
pf2 = header;
do {
if (pf1 == pf2)
return count;
pf1 = pf1->next;
pf2 = pf2->next;
count ++;
} while(true);
}
main 函数和相关define
#include <stdio.h>
#include <malloc.h>
typedef struct link{
link * next;
} linklist;
int main(void) {
int ret = 0;
linklist * header = (linklist *)malloc(sizeof(linklist));
if (header == NULL)
return -1;
ret = create_link(header);
if (ret)
return -1;
ret = find_cycle(header);
if (-1 == ret) {
printf("can not find the cycle: %d", ret);
return ret;
} else
printf("%d", ret);
return 0;
}