判断单链表是否有环及求环长和入环点(c语言实现)

判断单链表是否有环及求环长和入环点(c语言实现)

1. 判断单链表是否有环

创建两个指针 pfast 和 pslow,pslow每次移动一个节点,pfast 每次移动两个节点,比较两个指针指向的节点是否相同,相同则表示有环。如果pfast 或 pslow 为空,则说明没有环。

算法证明

见:Explain how finding cycle start node in cycle linked list work?
在这里插入图片描述
见上图,假设:

  1. 链表开头到入环点的距离为 m
  2. 入环点到首次相遇点的距离为 k
  3. 环的长度为 n
  4. pslow 走到与 pfast 的相遇地点共走了 i 步
    1. pfast 走到与 pslow 的相遇点共走了 j 步

可得到:
i = m + p n + k      ( p 0 ) i = m + p*n + k \ \ \ \ (p \geq 0)
j = m + q n + k      ( q 0 ) j = m + q*n + k \ \ \ \ (q \geq 0)

因为 pfast 走的速度是 pslow 的两倍,时间一样,所以路程也是 pslow 两倍,因此 j = 2i:
2 ( m + p n + k ) = m + q n + k 2*(m + p*n + k ) = m + q*n + k

得到:

m + k = ( q 2 p ) n m + k = (q - 2p) * n

可知,一定存在非负整数 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?

根据之前的证明结果:
m + k = ( q 2 p ) n m + k = (q - 2p) * n

当 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,( a 0 a \geq 0 ),
pfast 移动的距离为 j2 = x + bn, ( b 0 b \geq 0 ),

同样 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  
发布了120 篇原创文章 · 获赞 2 · 访问量 5791

猜你喜欢

转载自blog.csdn.net/Lee567/article/details/103652264
今日推荐