判断单链表中是否有环,找到环的入口节点
声明
- 文章可以随意转载,但请注明出处。
- 文中有一些地方引用了其他文章,但都已标明出处。如有侵犯,可立即删除。
- 文中有些地方并无冒犯之意,希望提及的博客作者理解。没有你们的帮助,对这个问题毫无头绪。
- 由于CSDN博客系统的内部错误,所有的公式后面都有一条恼人的竖线,实属无奈。
- 欢迎评论。
文章梗概
本文通过对现有资料的收集和整理,给出了一种相对简单的严格证明的“判断单链表是否有环,找到环的入口节点”的方法。
题目描述
一个链表中包含环,请找出该链表的环的入口结点(牛客网题目链接),题目中没有说是单链表,从给出的代码中可以看出是单链表而且不可修改链表元素的定义。
思考过程
从两个大的角度思考这个问题
1.记录遇到的每一个链表元素
在一次遍历过程中的,使用一种数据结构(数组、Hash表、基数树)记录遇到的每一个链表元素并判断是否已经遇到过,其中使用基数树可以获得 O(n) 的时间复杂度,但是可会有比较高的空间复杂度。
2.利用链表的性质
- 在题目的讨论页看到了比较有想法的一个答案冬至_的答案,正如其所言时间复杂度为O(n),两个指针,一个在前面,另一个紧邻着这个指针,在后面。两个指针同时向前移动,每移动一次,前面的指针的next指向NULL。也就是说:访问过的节点都断开,最后到达的那个节点一定是尾节点的下一个,也就是循环的第一个。这时候已经是第二次访问循环的第一节点了,第一次访问的时候我们已经让它指向了NULL,所以到这结束。这样做的话过题目是可以了,但是会破坏掉原来的链表,所以并不是一个特别完美的解决办法。如果可以在链表元素中加入一个记录“逻辑断开”的元素,也就是说在遍历的过程中,不真正的断开元素之间的连接,而是使用一个记录值,记录下“逻辑上的断开”。
- 另一个答案页上比较正统的回答为0909的回答,其主要思想和蒙恩的罪人的新浪博客大同小异,优雅简洁,后面主要针对蒙恩的罪人的新浪博客进行讨论。
相关问题的解法与证明
给定一个链表,只给出头指针
h
1.如何判断是否存在环?
使用追赶的方法,设定两个指针 slow、fast ,均从头指针开始,每次分别前进1步、2步。如存在环,则两者相遇;如不存在环, fast 遇到 NULL 退出。其中主要的思想就是“环形相遇追及问题”,理解上应该不复杂。
2.如何知道环的长度?
记录下问题1的相遇点, slow、fast 从该点开始,再次相遇时 slow 所经过的节点数就是环的长度。从环上的任意一点开始, slow、fast 再次相遇时 slow 经过的节点数就是环的长度,因为此时 slow、fast 起始距离为环长,速度差为 1 。选择问题1的相遇点为起始点是为了确保起始点为环上的一点。
3.如何找出环的连接点在哪里?
设问题1中的相遇点为 m1 ,赋值 p=m1 , q=h ,其中 h 为链表头结点,然后 p,q 每次1步向前运动, p,q 再次相遇所在的位置就是环的入口节点(环的连接点)。这里和上面提到的博客中的叙述差别非常大,这也是其有些问题的地方,我在这里更正了其说法,并给出了相对严格的证明。
相对简洁的实现
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
代码及问题三的证明
我们把该链表抽象为这样一个模型,假设环长为 n 。
情景1
此图表示其实状态,其中
h
表示链表头节点,
slow,fast
,起始状态指向
h
,
t
表示环的入口节点。
情景2
此图表示
slow
运动到了
t
,
fast
运动到
m1
,节点
h
和节点
t
之间的距离为
a
,节点
t
和节点
m1
之间的距离(弧长)为
b
,并设此时
fast
在环上做了
r
次圆周运动(因为
a
和
n
的长度都不固定,多以
fast
可能已经在环上运动了好多圈了)。相对于
slow
其运动的距离为:
a
由于
fast
速度是
slow
的二倍,所以其运动的距离(步数)为:
2a
并且,经过观察可知
fast
运动的距离为:
a+nr+b
,所以可知公式①:
情景3.
此时,
slow,fast
相遇在节点
m2
,也就是代码中
10
行判断成立的地方。其中
m2
为相遇点,
b
还是为弧长。由于链表的指针是有方向的,我们约定在环上计算距离的时候按照逆时针计算,也就是说,从
t
到
m1
的距离为
b
,从
m1
到
t
的距离为
n−b
(其中
n
为环的长度)。
同理在情况2中,从
fast
到
slow
的距离为
n−b
,它们的速度差为
1
,所以它们再次相遇的时候经过的时间为
n−b1=n−b
,
slow
经过的距离为
(n−b)×1=n−b
,所以假设相遇点为
m2
,那么显而
m2
到
t
的距离为
b
。
情景4.
情况4对应着代码中的
11
~
19
行。因为通过上面的讨论,如果能让
q
向前运动:
b+xn
步,那么
q
的位置恰好是
t
,其中
x∈{0,1,2,3,⋅⋅⋅}
。
值得高兴的是,在情况2中我们有公式①,观察到
a
恰好符合这样一个步数值,所以我们让
p=h
,
p,q
,都每次向前移动
1
,当他们相遇的时候恰好就是环的入点
t
,也就是说
p
从
h
移动到
p,q
再次相遇在这里的作用是提供一个计数。
所以,当
p
,
q
再次相遇的时候,他们的相遇点恰好了
t
,也就是需要找的环的入口点。
复杂度
我们关注第一次循环的 slow 和第二次循环的 p ,因为它们都是每次前进一步,由它们移动的步数,可以得到算法的时间复杂度,所以易知时间复杂度为 O(n) ,空间复杂度为 O(1) 。
http://blog.csdn.net/u011373710/article/details/54024366