怎样检测链表中存在循环?

这应该是一个比较老的题目,在一次面试的过程中碰到过该题目,并没有能够给出最佳的答案。当时面试时没有其他条件的限制,却只能给出了几乎是最差的答案;当看到这个命题一步一步的限制的时候,我也只能给出第三步的答案。

昨天有幸又翻到了这个题目,自己顺手做做,居然仍然没有太多的进步,最佳答案的思路在脑海里面已经不复存在,今天就拿这个题目温习一下,有兴趣的人也可以一步一步的思考下去看看。(在《编程之美》中也是有些题目不能给出最佳答案,甚至看到答案后过些日子就忘记了思路。)

题目:怎样才能检测到链表中存在循环?

通常的第一种答案:对链表的访问的每个元素做个标记(修改链表数据结构,增加一个标识),遍历链表,如果遇到已经标记过的元素,则说明链表存在循环。

注:这应该是最简单的方案,也是在具体实现中可能采用最多的方案;虽然对每个元素增加了标识,但却是时间复杂度最低的一种方案,只需遍历一遍!然而不幸的是,我在第一次面试的时候,却没有想到这个方案,虽然在实际中早就类似的方式改写过别人的代码。

第二个限制:链表位于只读区域(无法做标记)

通常第二种答案:建立一个动态数组,访问每个元素,然后存储在数组中。检查每一个后续的元素,然后从头查找这个数组,检查是否存在数组中。(书上给的答案说一些可怜的程序员会纠缠如何用散列表来优化数组访问的细节之中,结果在这里卡了壳。)

注1:幸运的是,我不是那些可怜的程序员,我在面试中直接给出了这个答案。但是这个答案远远不能令我满意,在实时应用中,这样的查找往往是灾难性的!每一个元素的的遍历是1~N的搜索过程,不是CPU就是程序员会吐血!

注2:远远不满意自己的答案,有没有更好的办法解决这个问题呢?改变检索过程,会不会大大缩短每个元素的1~N的遍历过程,有可能,那就是散列表!试着建立一个哈希算法与一张哈希表,或者是二次哈希表:每次检索时先计算哈希值,然后看看该值是否在哈希表中,如果在,那么就比较数值是否一直(甚至再次哈希,然后再比较),否则就可以查看下个链表元素。(这可能是一个好的解决方案,速度上应该有很大的提高。但高效哈希算法的实现,依赖于链表数据的特征;另外,不幸的是,这只是我的猜测,我没有写过这样的程序)

第三个限制:内存非常有限哦!(但假定了如果出现循环,肯定出现在前N项中)

通常第三种答案:只建立一个指针,指向链表头部!每次遍历链表的一个元素,就从该指针指向的链表头部比较一遍,知道第N个元素!

注:在书中对该提问给出的提示是:如果程序员能走到这一步。幸运的是,好像如果有限制的时候,我也能差不多给出这个答案。但是算法实现乏善可陈,效率极其低下!

第四个限制:内存就是非常有限!而且链表足够长,循环可能出现在任何地方!

最后答案:无!除了照抄上面的答案,我实在想不出任何的其他的答案!我脑海里只剩下一个线索:存在循环的元素可能内存地址相同。但我没有办法使用这个条件。

注:幸运的是,书中对该提问给出的提示是:即使优秀的候选者也会在这里碰壁!还好,自己还不算是太烂!当然我留给自己的最后一个线索,实际上并没有太多的用途,只是用来做了最后的判据而已!

症结:的链表中存在循环的最重要特征是什么?是一旦进入循环永远都跳不出这个循环!永远!

我应该设法记住这个答案,也是写本文的目的。

书中给出的答案:设置两个指针P1,P2。

1. 排除一个特殊情况,就是只有三个元素(实际上只有两个),第二个元素的next是第一个元素!P1指向第一个元素,p1的next为第二个元素,P1的next的next原色为第三个元素。令P2指向第三个元素,看看他们相等与否?

2. 如果不等,把P1指向P1的next元素(后移一个元素);P2指向next的next元素(后移两个元素)。如果存在循环,那么P2先进入循环并再也不会出来,而后P1进入循环,周而复始,其中一个指针终会追上另外一个,出现P1==P2的情况,则存在循环!否则,P2首先出现NULL,则说明该链表不存在循环。

     :虽然该方法可能要在存在的循环的链表里兜N个圈子,然后才能检测出来!但是时间复杂度仍然仅次于第一种方式!可是别忘记,他的空间复杂度为0!其他可是都是N!

你可能感兴趣的:(algorithm)