寻找有环单链表的入口节点

来源:互联网 发布:淘宝高达模型的黑店 编辑:程序博客网 时间:2024/05/29 08:38

问题陈述:一个可能存在环的单链表,寻找环的入口节点。

链表节点描述:

struct ListNode {    int num;    ListNode * next;};

这一问题有许多解法,这里说明三种『记数法』『断链法』『差速法』,各有优劣。前两种方法相对容易理解,第三种方法则相对优雅一些,建议直接看第三种方法。同时,这一问的算法也可以用来判断链表是否存在环。


一、记数法

这是最容易想到的方法,基于这样一个既定的事实:点链表的环一定是单环(最简单的那种),而且链表的尾节点一定指向环的入口节点。这样的环只有三种形式:数字0型,数字6型,数字9型。
这样看来,问题似乎就变成了寻找单链表的尾节点。然而可惜的是,由于环的寻在,尾节点已是不可寻了。
由上面的事实,我们还能得出另一个结论:只有环的入口节点有两个指针指向它。
这便是记数法和断链法的理论依据了。
所以技术法的思路就是:声明一个字典,C++中为map,遍历链表,记录每个节点被指向的次数,一旦发现被指向了两次的节点就返回该节点结束循环。
这各方法的缺点就是需要额外的存储空间来存储额外的数据,明显的土豪做法。
参考代码:

ListNode* EntryNodeOfLoop(ListNode* pHead) {  if(!pHead) 
return null;  map<ListNode*,int> flag;  while(pHead) {    if(++flag[pHead] == 2)      return pHead;    pHead=pHead->next;  }  return null;}


二、断链法

断链法的依据也是链表中只有环的入口节点由两个指针指向它。这样,我们就可以遍历链表,依次删除节点之间的连接,直到遇到空指针,最后这个节点一定就是环的入口节点。

很明显,这样的做法会破环链表本身的结构,属于伤敌一万,自损八千的做法。

参考代码:

ListNode* EntryNodeOfLoop(ListNode* pHead) {if(pHead == null|| pHead.next == null)
return null;ListNode* fast=pHead->next;ListNode* slow=pHead;while(fast != null) {slow->next = null;slow = fast;fast = fast->next;}return slow;}
这个方法还有一个致命的缺点,就是当链表中没有环的时候,它返回的是尾节点,但此时的尾节点并不是环的入口。


三、差速法

所谓的差速就是指两个移动速度不一样的快慢指针。在讲述这个算法之前,首先需要一点点的数学证明。


上图是一个简单有环单链表的模型图,总长度为L,无环部分长度(图中灰色部分)为m,环的长度(图中黑色加橙色部分)为n,红点为环的入口节点。
现在有两个指针,一个每次移动一个节点,称为慢指针,另一个的速度是前者的两倍,称为快指针。两个指针同时从链表头节点出发,它们必在环中相遇。图中绿色节点为慢指针刚到达环入口节点时快指针的位置,此时距离慢指针的距离(图中红绿两点间黑色部分)为x1,蓝色点表示快慢指针在环中相遇的位置,此时距离环的入口节点距离(图中红蓝两点间黑色部分)为x,沿着运动方向距离入口节点距离(图中橙色部分)还有y。
上面是对图的说明,下面开始证明一个结论:
m = k * n + y. (k为正整数)

当快慢指针相遇时,假设慢指针走了t步,那么快指针一定走了2t步。
当慢指针进入以后,快指针一定会在慢指针走完一圈之前追上它。因为最坏的情况是慢指针进入环时快指针刚好落后它一整圈,这样慢指针走完一圈快指针刚好追上慢指针。

快慢指针相遇时,慢指针走过的距离是:
t = m + x

当慢指针刚进入环入口时,快指针走过的的距离是:
t1 = m + k * n + x1. (k为正整数)

当快慢指针相遇时,快指针又走过了:
t2 = n + x2

所以快慢指针相遇时,快指针一共走过了:
t1 + t2 = m + k * n + x1 + n + x2 = m + (k + 1) * n + x = m + k * n + x. (k为正整数)

而相遇时快指针走过的距离是慢指针的两倍。
2 * t = t1 + t2
2(m + x) = m + k * n + x
m = k * n - x
m = (k - 1) * n + (n - x)
m = k * n + y.  (k为正整数)

这个结论说明,从头节点到环入口的距离等于快慢指针相遇处继续走到环入口(图中橙色部分)的距离加上环长度的整倍数。如果有一个人从链表头节点开始每次移动一个节点地往后走,另一个人从快慢指针相遇处(图中蓝色点)以同样的速度往前走,结果就是,两人相遇在环的入口节点处。
参考代码:
ListNode* EntryNodeOfLoop(ListNode* pHead) {if(pHead == null|| pHead.next == null|| pHead.next.next == null)
return null;ListNode* fast = pHead->next->next;ListNode* slow = pHead->next;while(fast! = slow) {if(fast->next != null && fast->next->next != null) {fast = fast->next->next;slow = slow->next;}
else{return null;        }}fast = pHead;while(fast != slow){fast = fast->next;slow = slow->next;return slow;}
结束。
原创粉丝点击