首页 > 代码库 > 《编程之美》3.6判断链表是否相交之扩展:链表找环方法证明
《编程之美》3.6判断链表是否相交之扩展:链表找环方法证明
先看看原题:《编程之美》3.6编程判断两个链表是否相交,原题假设两个链表不带环。
为了防止剧透使得没看过原题目的读者丧失思考的乐趣,我把最好的解法隐藏起来。由于这个问题本身的解答并不是本文的重点,扩展问题也采用这种形式呈现。
注:位于(*)符号之间的文字出自于:http://blog.csdn.net/v_july_v/article/details/6447013,作者v_JULY_v。
用指针p1、p2分别指向两个链表头,不断后移;最后到达各自表尾时,若p1==p2,那么两个链表必相交
用指针p1、p2分别指向两个链表头,不断后移;最后到达各自表尾时,若p1==p2,那么两个链表必相交
扩展问题1:如果链表可能有环,上面的方法怎么调整?
分情况讨论:如果两个链表都没有环,那么同原算法;如果两个链表一个有环,一个没环,那么必然不相交。(*)如果两个链表都有环,判断一个链表环上的任一点是否在另一个链表上,如果是,则必相交,反之不相交。这时,需要找到另一个链表完整的环都包括了哪些结点,才能进行判断。(*)可以看出,解答这个问题要解决判断是否有环。
分情况讨论:如果两个链表都没有环,那么同原算法;如果两个链表一个有环,一个没环,那么必然不相交。(*)如果两个链表都有环,判断一个链表环上的任一点是否在另一个链表上,如果是,则必相交,反之不相交。这时,需要找到另一个链表完整的环都包括了哪些结点,才能进行判断。(*)可以看出,解答这个问题要解决判断是否有环。
扩展问题2:如果必须要求出两个链表相交的第一个节点呢?
(*) 思路:如果两个尾结点是一样的,说明它们有重合;否则两个链表没有公共的结点。
在上面的思路中,顺序遍历两个链表到尾结点的时候,我们不能保证在两个链表上同时到达尾结点。这是因为两个链表不一定长度一样。但如果假设一个链表比另一个长L个结点,我们先在长的链表上遍历L个结点,之后再同步遍历,这个时候我们就能保证同时到达最后一个结点了。由于两个链表从第一个公共结点开始到链表的尾结点,这一部分是重合的。因此,它们肯定也是同时到达第一公共结点的。于是在遍历中,第一个相同的结点就是第一个公共的结点。
在这个思路中,我们先要分别遍历两个链表得到它们的长度,并求出两个长度之差。在长的链表上先遍历若干次之后,再同步遍历两个链表,直到找到相同的结点,或者一直到链表结束。PS:没有处理一种特殊情况:就是一个是循环链表,而另一个也是,只是头结点所在位置不一样。 (*)
对于特殊情况,相交的第一个结点可以是第一个链表的表头,也可以是第二个链表的表头。因为从表头开始二者就开始相交了。对于这种情况,如果判断出二者都是循环链表,就可以直接返回其中之一的头指针。
扩展问题2解法
(*) 思路:如果两个尾结点是一样的,说明它们有重合;否则两个链表没有公共的结点。 在上面的思路中,顺序遍历两个链表到尾结点的时候,我们不能保证在两个链表上同时到达尾结点。这是因为两个链表不一定长度一样。但如果假设一个链表比另一个长L个结点,我们先在长的链表上遍历L个结点,之后再同步遍历,这个时候我们就能保证同时到达最后一个结点了。由于两个链表从第一个公共结点开始到链表的尾结点,这一部分是重合的。因此,它们肯定也是同时到达第一公共结点的。于是在遍历中,第一个相同的结点就是第一个公共的结点。 在这个思路中,我们先要分别遍历两个链表得到它们的长度,并求出两个长度之差。在长的链表上先遍历若干次之后,再同步遍历两个链表,直到找到相同的结点,或者一直到链表结束。PS:没有处理一种特殊情况:就是一个是循环链表,而另一个也是,只是头结点所在位置不一样。 (*) 对于特殊情况,相交的第一个结点可以是第一个链表的表头,也可以是第二个链表的表头。因为从表头开始二者就开始相交了。对于这种情况,如果判断出二者都是循环链表,就可以直接返回其中之一的头指针。
相关问题:求链表倒数第k个结点
(*)设置两个指针p1,p2,首先p1和p2都指向head,然后p2向前走k步,这样p1和p2之间就间隔k个节点,最后p1和p2同时向前移动,直至p2走到链表末尾。(*)
(*)设置两个指针p1,p2,首先p1和p2都指向head,然后p2向前走k步,这样p1和p2之间就间隔k个节点,最后p1和p2同时向前移动,直至p2走到链表末尾。(*)
现在来看,遗留问题是:1.如何判断链表有环?2.如何找到链表环的入口?方法是有的,而且这个算法在3.11节程序改错的扩展问题有所提示。简单来说就是:
对于问题1,指针p1、p2指向表头,每次循环p1指向后继,p2指向后继的后继;循环的结束条件是,p2后继为空(无环)或p1==p2(有环)。
这个方法网上没有看到比较令我满意的解释,而《编程原本》(Elements of Programming)在第2章“变换及其轨道”中虽然有简单的说明,可当时看的时候也不是特别理解(就算现在理解了,这本书上讲得比较抽象,上面的说明放在这里只能让读者越看越糊涂),可能是悟性不够吧。一是不满足于用举例说明,二是觉得,如果是连续地移动,即像物体运动时位移和时间是连续的而不是这样离散的,当然会相遇;而二者都是离散的,如果每次都发生p2刚好越过p1的情况呢?下面用易于理解的方式证明,这个解法中如果有环,p1和p2必同时在停留在某个节点。
如左图,在任意时刻,p1和p2都在环上。由于p1每次向前1步,p2每次向前两步,用相对运动的观点来看,把p1看作静止,那么p2每次相对p1向前1步,二者在顺时针方向上的距离每经过一个时刻就减少1,直到变为0,也即二者恰好相遇。这样就证明了在离散情况下,对于有环链表,二者也是必然在某一时刻相遇在某个节点上的。
在相遇点,有(2L+2x-L)%R == x%R,即(L+2x)%R==x%R。根据同余的性质,(L+x)%R==0。
此时把p1放回表头,p2的速度降为1。当p1走了L到达环的入口时,p2在环上的位置为(x+L)%R==0,这意味着p2回到了入口,且与p1相遇。并且由于之前p1不在环上,这是二者的在这一步操作后的第一次相遇,并且都在入口,这便找出了入口的位置。
重述一遍寻找环存在和环入口的方法:
用两个指针p1、p2指向表头,每次循环时p1指向它的后继,p2指向它后继的后继。若p2的后继为NULL,表明链表没有环;否则有环且p1==p2时循环可以终止。此时为了寻找环的入口,将p1重新指向表头且仍然每次循环都指向后继,p2每次也指向后继(即减速为1)。当p1与p2再次相等时,相等点就是环的入口。
http://www.cnblogs.com/wuyuegb2312/p/3183214.html
《编程之美》3.6判断链表是否相交之扩展:链表找环方法证明