昨天做了一个小总结,发现单链表这块关于带环链表漏掉了,今天就来总结一下。
要寻找一个单链表的环的入口点,首先得判断一个链表是否带环,那么又怎样判断一个单链表是否带环呢?
首先我们得普及一下环有什么特性,我们知道无环的单链表有首节点和尾节点,那么带环的单链表呢?显然还是有首节点的,但是没有尾节点吧。
如图所示:
带环的单链表既然没有尾结点,那么要是遍历的话,就会无限循环,我们就没法通过这样来判断这是带环的,那么我们就要想一个办法让其停下来。
我们知道,运动会三千米赛跑的时候,第一名总会超过最后一名一圈,他们都是从同一个起点开始跑的,最后跑的快的追上了跑的慢的。这就是圈的特性,那么我们是不是也可以使用这一点来判断单链表是否带环。
我们定义一个快的指针,让其一步走两个节点,再定义一个慢的节点,一步走一个节点,这样,如果链表带环,那么总有一个地方让这两个指针是相遇的;如果不带环那么快的会先到尾结点。
那么接下来就来实现吧:
typedef struct LinkNode //节点
{
DataType data;
struct LinkNode *next;
}LinkNode,*pLinkNode;
typedef struct LinkList //链表
{
LinkNode *pHead;
}LinkList,*pLinkList;
pLinkNode isCircle(pLinkList plist) //判断是不是带环链表
{
assert(plist);
if (NULL == plist->pHead)
{
printf("链表为空\n");
return NULL;
}
pLinkNode fast = plist->pHead;
pLinkNode slow = plist->pHead;
while (fast && fast->next) //如果快的指针先到尾结点,那么不是。(奇偶)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow) //相遇,那么是带环的链表
return fast;
}
return NULL;
}
判断是不是带环的链表很简单,只要理解了这个过程,没啥问题,那么知道了链表是带环的链表,如何来找带环链表的入口呢?
这涉及不太高深的数学问题,作为数学专业的我,还是很乐意讲解的,首先来看下图:
我们先将一些未知量设出来,这样就可以进行运算了:
首先可以列出一个简单的等式:a+r=L ①
那么再根据两个指针来列一个等式: a+n*r+x=(a+x)*2 ②
解释一下第二个式子:左半部分是快的指针相遇前走的路程首先走了一段 a 之后,在进入环中走了 n 圈,又走了x段路程和慢的指针相遇;慢的指针路程因为它的速度是快的指针的一半,所以时间相同的情况下,快的指针走过的路程当然是慢的指针走过的路程的二倍。
然后联合①、②得:a=n*r-x
这样假设n为1时,即——a=r-x,我们要是再定义一个指针,从表头开始遍历,同时,让慢的指针从相遇点开始,那么当他们相遇的时候,就是入口点的地方。
下面我们来看代码:
pLinkNode firstCrossNode(pLinkList plist)
{
assert(plist);
if (NULL == plist->pHead)
{
printf("链表是空\n");
return NULL;
}
pLinkNode ret = isCircle(plist); //接收相遇点的指针
if (ret == NULL)
{
printf("链表不带环\n");
return NULL;
}
pLinkNode start = plist->pHead; //从表头开始
pLinkNode slow = ret; //从相遇点开始
while (start)
{
start = start->next;
slow = slow->next;
if (start == slow)
return start; //返回入口点的指针
}
}