单链表是否有环

本文详细介绍了如何判断带头结点的单链表是否存在环,并通过算法实现了判断过程。进一步,文章阐述了如何利用环检测算法找出环的入口结点及计算环的长度,涉及链表操作和数据结构原理。
题目:判断一个带头结点的单链表L是否有环

解题思路:
咋看之下,这题还真不好解,于是又一些投机取巧的人,在访问单链表中元素的时候,用一个比较大的数N控制访问元素的个数,他们认为如果在访问到第N个元素之前有一个元素为null,那么单链表没有环,反之,单链表有环。他们的解释是都访问了这么多元素了,还没有一个元素为空,单链表很有可能有环,再加上一般单链表中的元素都不超过N个,所以单链表有环。对于这种解答方式,我们也只能呵呵了。

要判断单链表有没有环,肯定会判断两个指针是否相等。而且这两个指针从头结点位置开始走的步数肯定不一样。可以设两个指针slow,fast,slow从头结点开始往后走,每次走一步,同时fast从头结点开始往后走,每次走两步,这两个指针一直往后面走直到fast为空,则单链表无环,或者slow与fast相等,则单链表有环。这个过程可以保证slow会走过所有在它之前的结点,同时fast比slow多走的步长从1开始每次递增1,而且当它们相遇时多走的步长就是环中结点的个数的正整数倍。


ADT定义如下
#define ElemType int
typedef struct LNode{
    ElemType data;
    LNode *next;

}LNode,*LinkList;


算法实现:

bool hasCircle(LinkList &L)
{
	if(L==NULL) return false;//单链表为空时,单链表没有环
	if(L->next==NULL) return false;//单链表中只有头结点,而且头结点的next为空,单链表没有环
	LNode* p=L->next;//p表示从头结点开始每次往后走一步的指针
	LNode* q=L->next->next;//q表示从头结点开始每次往后走两步的指针
	while(q) //q不为空执行while循环
	{
		if(p==q) return true;//p与q相等,单链表有环
		p=p->next;
		q=q->next->next;
	}
	return false;
}

拓展:判断带头结点的单链表是否有环,并找出环的入口结点

解题思路:

不妨设从头结点到环的入口结点需要走a步即环外包括头结点在内总共有a个结点,环中有b个结点,假设slow走了s步后,slow与fast第一次相遇。它们肯定是在环内相遇,而且相遇时slow没有从环的入口结点再次走到环的入口结点,假设在环中距离环入口结点d步长距离相遇,设相遇结点为meet。

此时令头结点指向fast,让slow与fast每次往后走一步,当它们再次第一次相遇时,相遇的结点就是环的入口结点。

证明如下:

当slow再走s步后会再次到meet结点,而此时fast走了s步后也会首次到达meet结点,它们相遇,因为两者都是每次同时走一步,那么从fast进入环中开始,fast与slow就一直相遇,它们首次相遇的结点就是环的入口结点。

//当单链表中没有环时返回NULL,有环时返回环的入口结点
LNode* searchEntranceNode(LinkList &L)
{
	if(L==NULL) return NULL;//单链表为空时,单链表没有环
	if(L->next==NULL) return NULL;//单链表中只有头结点,而且头结点的next为空,单链表没有环
	LNode* p=L->next;//p表示从头结点开始每次往后走一步的指针
	LNode* q=L->next->next;//q表示从头结点开始每次往后走两步的指针
	while(q) //q不为空执行while循环
	{
		if(p==q) break;//p与q相等,单链表有环
		p=p->next;
		q=q->next->next;
	}
	if(q==NULL) return NULL;

	//这里之所以没有向上面一样,先让p,q走一步再进入循环判断,是因为头结点可能就是环的入口结点
	q=L;
	while(q)
	{
		if(p==q) return p;//返回环中入口结点
		p=p->next;
		q=q->next;
	}
	return NULL;
}


拓展:判断带头结点的单链表是否有环,并求环的长度

解题思路:

设一个指针q指向环入口结点,让q往后移动直到q再次等于环的入口结点,此时q所走的总步数就是环的长度

int circleLength(LinkList &L)
{
	LNode* p=searchEntranceNode(L);//找到环的入口结点
	if(p==NULL) return 0;//不存在环时,返回0
	LNode* q=p->next;
	int length=1;
	while(p!=q)
	{
		length++;
		q=q->next;
	}
	return length;//返回环的长度
}



判断单链表是否的方法有以下几种: #### 追及法(快慢指针法) 设置两个指针,一个是 `slow`,另一个是 `fast`。`slow` 一次走一个节点,`fast` 一次走两个节点,当 `fast` 追上 `slow` 即 `fast` 和 `slow` 重合时,说明链表中有 [^4]。 以下是Python代码示例: ```python class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next def hasCycle(head): slow = head fast = head while fast and fast.next: slow = slow.next fast = fast.next.next if slow == fast: return True return False ``` #### 哈希法 维护一个哈希表,遍历链表将每个节点的地址存入哈希表中,如果出现重复就代表链表有 [^4]。 以下是Python代码示例: ```python class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next def hasCycle(head): visited = set() while head: if head in visited: return True visited.add(head) head = head.next return False ``` #### 标记法 遍历单链表,将经过的节点都做标记,采用的方法是将遍历的节点都放入集合 `visited` 中,对每个节点判定是否在集合中出现过,若出现重复访问,则判定有。空间复杂度为 $O(n)$,时间复杂度为 $O(n^2)$ [^3]。 以下是Java代码示例: ```java import java.util.ArrayList; class Node { int val; Node next; Node(int x) { val = x; next = null; } } public class Main { private static boolean methodSecond(Node head) { ArrayList<Node> visited = new ArrayList<>(); while (head != null) { if (visited.contains(head)) { return true; } visited.add(head); head = head.next; } return false; } } ``` #### 链表合并转化法 先遍历第一个链表到它的尾部,然后将尾部的 `next` 指针指向第二个链表(尾部指针的 `next` 本来指向的是 `null`),这样两个链表就合成了一个链表,判断原来的两个链表是否相交也就转变成了判断新的链表是否的问题 [^1]。 #### 反转链表法 反转链表时,会用到3个指针,分别为指向遍历时当前的结点的 `current` 指针,指向反转后的子链表的头结点的指针 `temp`,及指向遍历时当前结点的下一个结点的 `next` 指针,如果在反转时,出现了 `next` 指向头结点的情况,那么肯定是有的 [^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值