1. 判断单链表是否有环
创建两个指针 pfast 和 pslow,pslow每次移动一个节点,pfast 每次移动两个节点,比较两个指针指向的节点是否相同,相同则表示有环。如果pfast 或 pslow 为空,则说明没有环。
算法证明
见:Explain how finding cycle start node in cycle linked list work?
见上图,假设:
- 链表开头到入环点的距离为 m
- 入环点到首次相遇点的距离为 k
- 环的长度为 n
- pslow 走到与 pfast 的相遇地点共走了 i 步
-
- pfast 走到与 pslow 的相遇点共走了 j 步
可得到:
因为 pfast 走的速度是 pslow 的两倍,时间一样,所以路程也是 pslow 两倍,因此 j = 2i:
得到:
可知,一定存在非负整数 p 和 q 使得等式成立,如:p 为 0,q 为 m,k 为 mn - m 时等式成立。这组数据并不一定是使 i 最小的数据,但能证明判断是否有环的算法的正确性。
2. 求入环点
在两个指针首次相遇后,将 pslow 移到头节点,pfast 仍在相遇位置,两个指针都以 pslow 的速度移动(每次移动一个节点),则两者第一次相遇的位置即为入环点。
证明
见:Explain how finding cycle start node in cycle linked list work?
根据之前的证明结果:
当 pslow 从头节点移到两者第一次相遇的地方时走过的路程为 m + k,该段路相当于走了 q - 2p 圈环的路程,因此在该断时间内,pfast 以与 pslow 同样的速度移动了 q - 2p 圈环再次在相同地方相遇。
因此,如果 pslow 只移动 m 的距离,则 pfast 以相同的速度从第一次相遇点开始移动了 p-2q 圈 减去 k 的距离,正好在入环点的位置。
因此 pslow 和 pfast 在新的位置以 1 的速度同时移动,第一次相遇的位置即为入环点。
3. 求环长
回到 pfast 与 pslow 第一次相遇的时刻,在该地点 pslow 以每次移动一个节点的速度绕环移动,同时 pfast 以两倍的速度移动,则第二次相遇的时刻,pfast 比 pslow 多走一圈,pslow 走的距离即为环长。
证明
假设两指针第一次相遇地点为 p,pslow 以速度 1 移动,pfast 以速度 2 移动,第二次相遇地点为 q,p 与 q 的距离为 x,则:
pslow 移动的距离为 j1 = x + an,(
),
pfast 移动的距离为 j2 = x + bn, (
),
同样 j2 = 2j1,因此:x + bn = 2 * (x + an),得到:x = (b - 2a)n。
因此 x 最小值为 n,此时 a = 1, b = 2。即两个指针再次相遇,则 pslow 至少走一圈。
综上,求环长直接让 pslow 继续移动直到回到原来位置。
4. 程序
/*
测试单链表是否有环,求入环点以及环长
创建一个单链表:1 2 3 4 5 6 7,
然后让尾节点 7 和节点 3 相连成为一个环
入环点数据为 3, 环长为 5:3 4 5 6 7
*/
#include<stdio.h>
#include<stdlib.h>
struct node;
typedef struct node *ptrtonode;
typedef ptrtonode list;
typedef ptrtonode position;
struct node {
int data;
ptrtonode next;
};
list InitList(void);
void CreatList(list L);
position HasCycle(list L);
position CycleStart(list L, position meet);
int CycleLen(list L, position meet);
position CycleTail(position meet, position cyclestart);
void ShowList(list L);
void DeleteList(list L);
int main(void)
{
list L = InitList();
CreatList(L); //创建带环的链表
position meet = HasCycle(L);
if (meet == NULL) {
printf("List hasn't cycle.\n");
puts("List:");
}
else {
position cyclestart = CycleStart(L,meet);
printf("List has a cycle, and the cycle starts at"
" number %d\n",cyclestart->data);
int len = CycleLen(L,meet);
printf("The length of the cycle is %d\n",len);
position cycletail = CycleTail(meet, cyclestart);
cycletail->next = NULL;//将链表恢复无环状态
}
printf("Normal linked list numbers:\n");
ShowList(L);
DeleteList(L);
return 0;
}
list InitList(void)
{
list L = (list)malloc(sizeof(struct node));
if (L == NULL) {
fprintf(stderr,"Out of space.\n");
exit(EXIT_FAILURE);
}
L->next = NULL;
return L;
}
void InsertTail(position p, int n)
{
list tem = InitList();
tem->data = n;
tem->next = p->next;
p->next = tem;
}
void CreatList(list L)
{
position p = L;
for (int i = 0; i < 7; i++) {
InsertTail(p,i+1);
p = p->next;
}
//循环结束 p指向尾节点
//为链表建一个环
position tem = L->next->next->next;
p->next = tem;
}
int IsEmpty(list L)
{
return L->next == NULL;
}
//判断是否有环,无环则返回空指针,有环返回指向相遇节点的指针
position HasCycle(list L)
{
if (IsEmpty(L)) {
printf("Empty linked list.\n");
exit(EXIT_FAILURE);
}
position pfast = L->next->next, pslow = L->next;
while (pfast && pslow) {
if (pfast == pslow)
return pfast;
pfast = pfast->next->next;
pslow = pslow->next;
}
return NULL;
}
position CycleStart(list L, position meet)
{
position p1 = L, p2 = meet;
while (p1 != p2) {
p1 = p1->next;
p2 = p2->next;
}
return p1;
}
int CycleLen(list L, position meet)
{
int ct = 1;
position p = meet;
while (p->next != meet) {
p = p->next;
ct++;
}
return ct;
}
position CycleTail(position meet, position start)
{
position p = meet;
while (p->next != start)
p = p->next;
return p;
}
void ShowList(list L)
{
position p = L->next;
while (p) {
printf("%d ",p->data);
p = p->next;
}
putchar('\n');
}
void DeleteList(list L)
{
position p = L->next, tem;
L->next = NULL;
while (p) {
p->next = tem;
free(p);
p = tem;
}
}
测试结果:
List has a cycle, and the cycle starts at number 3
The length of the cycle is 5
Normal linked list numbers:
1 2 3 4 5 6 7