一、前言
在数据结构中,我们会学到很多结构,例如顺序表、链表、树、环等,但是本章要重点证明的是链表中是否有环的这一问题,接下来我们一起来学习本章知识。
二、链表结构
如下图所示,这是一副带环无头单链表。
图一:
草图:
图二:
图一中,其结构是一个无头带环单链表,图二中,其结构是一个普通的单链表,没有环。那么要想判别有环和无环在逻辑上要如何证明呢?
三、证明
首先对于无环的普通单链表,要想判别他的结构很简单,只需让一指针从前往后遍历,当指针为空时,就说明到了尾部,也就说明不是带环单链表。代码实现如下:
//找尾节点
void FindBack(LinkList* linklist)
{
assert(linklist);
while (linklist)
{
linklist = linklist->next;
}
}
但是如果对于带环单链表来说,我们就需要从中证明环的存在。
对于这样一个结构来说,我们可以清晰的看到在B点处同一个节点被两个指针访问,此后进入循环,但如果想证明有环的存在,我们引入两个快慢指针,分别为fast、slow。
开始时,让fast指针走两步,slow指针走一步,当fast达到B点进入环后,会在环内一直运动等待着slow指针,而当slow指针一旦进环,在一圈之内,fast就会追上slow,两点相遇的点视为meet。
如下图所示:
一旦两个指针相遇,我们就能确定这是一个带环的单链表。
结论:当fast走一步,slow走两步时,两个指针一定会相遇,首先fast会先进环,slow后进环,在环中,slow走一步fast走两步,每次移动两个指针之间的距离会缩小1,直到重合。
在图中我们把进环后fast和slow的距离设为N。
第一次两个指针的距离为N,第二次为N-1,第三次N-2......第N次距离为0,相遇!
以上阐述是我们按slow走一步,fast走两步所得到的结论,但是如果我们让slow走一步,fast走三步、四步、甚至更多呢,那么两个指针最终会不会相遇呢?
我们以slow走一步,flow走三步为例。
设定slow进环后,两个指针的距离为N,当两个指针每走一次,它们之间的距离就会缩减2,那么在指针运行时,就会发生两种情况,其一:两个指针正好相遇,证明有环,其二:两个指针阴差阳错,错过了相遇的节点导致无法相遇。
所以我们举例说明,当两个指针初始时之间的距离为偶数时,假如slow在1的位置,fast在3的位置,那么必然会相遇,但是如果两个指针初始时之间的距离为奇数时,一旦两个指针错过,那么就有可能永远追不上了。
结论得出:
fast | slow | 是否一定会相遇 |
走两步 | 走一步 | 一定会相遇 |
假设环的长度为C,slow和fast的距离变成C-1,当C-1为偶数时,那么下一轮可以追上,如果C-1为奇数时那么就永远追不上了。