zoukankan      html  css  js  c++  java
  • 证明利用快慢指针寻找有环单链表中环的起点算法

    问题:给定一个有环单链表,找到链表中环的起点,也就是说,找到下图中的单链表中Join点:
    这里写图片描写叙述
    (本图来源于http://www.cnblogs.com/xudong-bupt/p/3667729.html。做了少许改动)
    解答:一个常见的解法是这种。声明两个指针,两个指针的初始值都是链表的头指针,当中一个指针每次前移两个节点。称为快指针,还有一个指针每次前移一个节点。称为慢指针,然后让它们两同一时候出发,由于链表中存在环,它们终于会第一次相遇。如果相遇在图中Pos点。相遇之后。慢指针从Pos点出发再往前移LenA个节点,就能到达Join点。终于得到我们的答案。
    我想。大家读完上面的问题和解答后都会有一个疑问,为什么快慢指针相遇后,慢指针再往前移LenA个节点就刚好到达Join点呢?本文就是来解决大家心中的疑惑的。
    网上解说利用快慢指针寻找有环单链表中环的起点算法的博文并不少,我看了从百度搜出来的前面的四五篇,还看了LeetCode上某人推荐的一个国外博文,可是没有一个把这点讲明确的。甚至有些都是错的,错的原因有两个:(1)没认识到LenA的长度可能比环的长度大,(2)没认识到两指针相遇时,快指针可能已经绕环好几圈了。扯了些废话。如今进入正题。证明分为两部分。
    (一)
    首先要证明的是。两指针相遇时。慢指针还没有走完整个链表。


    (1)如果慢指针第一次达到Join点时,快指针也在Join点。慢指针自然没有走完整个链表;
    (2)如果慢指针第一次达到Join点时。快指针没有在Join点,我们以最极端的情况来说,如果快指针这时就在慢指针的前面一个节点,这时,快指针追上慢指针须要走最长的距离。由于快指针的速度是慢指针的两倍,所以慢指针走一圈,快指针走两圈,当慢指针第一次在环上走完一圈回到Join点时,快指针刚好走完两圈。而且已经在慢指针的前面,所以它两在慢指针第一次回到Join点之前就已经相遇。
    终于,得出结论:两指针相遇时,慢指针还没有走完整个链表。


    (二)
    然后。我们来证明。快慢指针相遇后,慢指针再往前移LenA个节点就刚好到达Join点。
    如果第一次相遇点为Pos,环起点为Join。头结点到环起点的长度为LenA,环起点到第一次相遇点的长度为x,第一次相遇点到环起点的长度为y。环长为R。于是有以下结果:
    (1)第一次相遇时。slow走的长度 S = LenA + x;(由证明的第一部分得到)
    (2)第一次相遇时,fast走的长度 2S = LenA + n*R + x;(相遇时,快指针可能已经绕环好几圈了。至少一圈。n大于等于1。由于快指针先进入环。要追上后进入的慢指针。必须得回到环起点在起点之后才干追上)
    (3)LenA + x = n*R; LenA = n*R -x;
    当中,(3)是由(1)(2)推导出来的。
    我们的目标是依据上面三点得出慢指针在走了S + LenA后刚好到达Join点(这是清晰说明这个问题的关键)。我们尝试依据上面三点推导出我们想要的结论:
    S + LenA = S + n*R - x = S + (n - 1)*R + (R - x) = S + (n - 1)*R + y
    这个表达式证明了我们的结论:慢指针在移动S + (n - 1)*R个节点后刚好在快慢指针第一次相遇的位置,再移动y个节点后就刚好达到Join点。

    这里顺便给出算法的详细实现方法,以方便同学们阅读。

    实现的详细流程是这种:声明两个指针。两个指针的初始值都是链表的头指针,当中一个指针每次前移两个节点,称为快指针,还有一个指针每次前移一个节点,称为慢指针,然后让它们两同一时候出发。由于链表中存在环,它们终于会第一次相遇,然后把当中一个指针移回到链表头的位置。也就是设置为链表头指针,然后让两个指针一个从相遇点出发,一个从链表头节点出发。两个都一次走一步。一直走到两个指针第一次相遇为止,这时两个指针都走了LenA的长度。以下是Java实现:

    public static ListNode findLoopStart(ListNode nodeHead) {
    
            ListNode slowPointer, fastPointer;
            slowPointer = nodeHead;
            fastPointer = nodeHead;
    
            // 寻找第一次相遇点
            while (fastPointer != null) {
                fastPointer = fastPointer.next.next;
                slowPointer = slowPointer.next;
    
                if (fastPointer == slowPointer) {
                    break;
                }
            }
    
            // 把慢指针移动到链表的开头
            slowPointer = nodeHead;
    
            while (slowPointer != fastPointer) {
                slowPointer = slowPointer.next;
                fastPointer = fastPointer.next;
            }
            return slowPointer;
        }
    public class ListNode {
        public int val;
        public ListNode next;
    
        public ListNode(int x) {
            val = x;
        }
    
        @Override
        public String toString() {
            return "ListNode [val=" + val + ", next=" + next + "]";
        }
    
    
    }

    參考文章链接:
    http://www.cnblogs.com/xudong-bupt/p/3667729.html
    http://www.cnblogs.com/ccdev/archive/2012/09/06/2673618.html
    http://blog.chinaunix.net/uid-26448049-id-3046656.html
    http://learningarsenal.info/index.php/2015/08/24/detecting-start-of-a-loop-in-singly-linked-list/

  • 相关阅读:
    【转】HEIF图片存储格式探秘
    【转】Maven项目中将配置文件打包到jar包中
    C++ 单词接龙
    vector vector int 初始化
    哈夫曼树的特点
    哈夫曼树的构造
    单链表的逆转(测试数据)
    单链表的逆转
    二叉搜索树的插入
    二叉搜索数的应用
  • 原文地址:https://www.cnblogs.com/llguanli/p/8930671.html
Copyright © 2011-2022 走看看