检测一个较大的单链表是否有环
【阿里巴巴笔试题】
题目描述:
单链表有环指的是单链表中某个结点的next域指向的是链表中它之前的某一个结点,这样在链表的尾部形成一个环形结构。如何判断单链表是否有环?如何找到环的入口点?、
方法一:哈希集合法
思路:可以定义一个空集合,接着从头遍历这个单链表,如果当前结点的next不在集合中就继续往后走,如果在集合中,当前结点的next就是环的入口。
def check(link: SingleLink):
s = set() # 构造一个空的set
p = link.head
if p is None: # 空链表直接返回False
return False
s.add(p) # 不空的话把第一个结点加入到s中
while p.next is not None: # 如果当前结点的next不是空的话
if p.next in s: # 当前结点的下一个结点在集合中
return p.next.data # 入口就是当前结点的下一个结点
s.add(p.next) # 不在就把当前结点加入到集合中
p = p.next
return False # 如果一直到结尾都没有,那就不是环,返回False
方法二:快慢指针法
我们设置一个慢指针slow和一个快指针fast,慢指针每次向后走一步,快指针每次走两步,因为有环,所以快指针早晚会追上慢指针的。如下图(自己手画的):
我们可以清楚的看到,对于这个有环的单链表,快指针会走到结尾7之后再沿着环绕回来最终在5结点的位置追上慢结点,此时我们的fast和slow指针都指向这个5结点;
紧接着我们用一个新的变量a指向链表的开头,然后让a和fast同时往后走,每次都走一步,最终会在4结点相遇,这个4结点就是链表的环的入口
。这样我们就找到了环形链表的入口了。
这样简单理解很容易,其实这个思路后面是由Floyd算法
在做支撑的,Floyd判圈算法也称为龟兔赛跑算法,可以判断链表
、迭代函数
、有限状态机
是否有环,如果有就可以找出环的起点和大小,时间复杂度是O(n),空间复杂度是O(1)。
扩展链接:Floyd算法
本题代码实现:
# 快慢指针法
def check(link: SingleLink):
slow = fast = link.head # 快慢指针都指向 第一个结点
circle = False # 假设没有环
while slow and fast and fast.next is not None: #
slow = slow.next # 慢结点一次走一步
fast = fast.next.next # 快结点一次走两步
if slow and slow == fast: # 如果快结点等于了慢结点,那么就说明有环
circle = True
break # 直接出循环
if not circle:
return False # 如果没有环直接退出
# 有环,找到环的入口点
a = link.head # a从第一个结点开始
while a != fast: # fast此时是相遇的结点
a = a.next
fast = fast.next # a和fast都每次向后移动一步,直到相遇,相遇的地方就是环的入口
return a.data # 返回入口点的data
测试
先创建一个有环链表:
链表环的入口点是3
class Node: # 创建一个结点类,用来生成结点
def __init__(self, data):
self.data = data
class SingleLink: # 创建一个单链表类
def __init__(self):
self.head = None
self.tail = None
def append(self, x): # 尾部追加方法
if self.head is None:
self.head = self.tail = Node(x)
return self
self.tail.next = Node(x)
self.tail = self.tail.next
return self
# 构造一个有环链表
link1 = SingleLink()
for i in range(1, 10):
link1.append(i)
link1.tail.next = link1.head.next.next # 构造一个环
分别调用两个函数,查看打印结果:
# 快慢指针法
def check(link: SingleLink):
slow = fast = link.head # 快慢指针都指向 第一个结点
circle = False # 假设没有环
while slow and fast and fast.next is not None: #
slow = slow.next # 慢结点一次走一步
fast = fast.next.next # 快结点一次走两步
if slow and slow == fast: # 如果快结点等于了慢结点,那么就说明有环
circle = True
break # 直接出循环
if not circle:
return False
a = link.head # a从第一个结点开始
while a != fast: # fast此时是相遇的结点
a = a.next
fast = fast.next # a和fast都每次向后移动一步,直到相遇,相遇的地方就是环的入口
return a.data # 返回入口点的data
print(check(link1)) # 3
########################################
# 哈希集合法:
def check(link: SingleLink):
s = set() # 构造一个空的set
p = link.head
if p is None: # 空链表直接返回False
return False
s.add(p) # 不空的话把第一个结点加入到s中
while p.next is not None: # 如果当前结点的next不是空的话
if p.next in s: # 当前结点的下一个结点在集合中
return p.next.data # 入口就是当前结点的下一个结点
s.add(p.next) # 不在就把当前结点加入到集合中
p = p.next
return False # 如果一直到结尾都没有,那就不是环,返回False
print(check(link1)) # 3
测试结果没问题。
最近才从csdn迁徙到博客园,欢迎关注交流!
代码改变世界!