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

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

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

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

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

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

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

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

注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!

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 双向不循环链表(Doubly Linked List)是一种链表数据结构,它的每个节点除了包含一个指向下一个节点的指针外,还包含一个指向前一个节点的指针。 双向不循环链表的结构可以用下面的类似伪代码的方式表示: ``` class Node { value // 节点的值 prev // 指向前一个节点的指针 next // 指向下一个节点的指针 } class DoublyLinkedList { head // 指向链表头节点的指针 tail // 指向链表尾节点的指针 // 链表的基本操作 append(value) // 在链表末尾添加一个节点 prepend(value) // 在链表头部添加一个节点 insertAfter(node, value) // 在指定节点后插入一个节点 remove(node) // 移除指定节点 search(value) // 查找指定值的节点 } ``` 在双向不循环链表,头节点的 `prev` 指针为空,尾节点的 `next` 指针为空。因为每个节点都包含指向前一个节点和后一个节点的指针,所以在遍历链表时可以向前或向后遍历。这使得在双向链表插入、删除节点比单向链表更加高效,因为可以在不需要遍历整个链表的情况下进行操作。 ### 回答2: 双向不循环链表是一种链表数据结构,它与单向链表相比,每个节点都有两个指针:一个指向前一个节点,一个指向后一个节点。该链表的结构是一个前后链接的线性序列,即每个节点都包含一个数据元素和两个指针域。 首先,链表一个头节点和一个尾节点,它们并不存储数据元素,只作为链表的标记。 链表的每个节点都有三个部分:数据元素、指向前一个节点的指针和指向后一个节点的指针。 在双向不循环链表,除了头节点和尾节点,其他节点的前一个节点和后一个节点都存在。头节点的前一个节点指针为空,尾节点的后一个节点指针也为空。 双向不循环链表可以通过各种操作进行增删改查。插入操作可以在指定节点前或后插入新节点,只需修改节点的指针指向即可。删除操作可以根据节点的指针找到前后节点,将其链接起来。修改操作可以简单地修改节点的数据元素。查找操作可以从头节点开始,按顺序遍历每个节点,直到找到目标节点。 双向不循环链表适用于需要快速插入和删除节点的场景,因为只需修改节点的指针指向即可,不需要像数组那样移动其他元素。而且,因为可以从任意节点出发向前或向后遍历,所以查找操作的灵活性更高。然而,相对于数组,链表的存储空间要更多,并且访问节点的时间复杂度会较高。 ### 回答3: 双向不循环链表(Doubly Linked List)是一种数据结构,它与单链表相似,但每个节点有两个指针,一个指向前一个节点,一个指向后一个节点。 双向不循环链表的结构如下: 1. 节点(Node):每个节点包含两个部分,一个是存储数据的数据域(Data)和两个指针域(Prev和Next)。 - 数据域(Data):用于存储节点的数据。 - 指针域(Prev和Next):分别指向该节点的前一个节点和后一个节点。 2. 头节点(Head):双向链表的首节点称为头节点,它不存储实际数据,只是作为链表的入口,它的Prev指针为空,Next指针指向第一个节点。 3. 尾节点(Tail):双向链表的尾节点是最后一个包含数据的节点,它的Next指针为空,Prev指针指向倒数第二个节点。 4. 操作:双向链表支持多种操作,包括插入(Insertion)和删除(Deletion)。 - 插入操作:在指定位置(节点)之前或之后插入一个新的节点,调整相邻节点的指针。 - 删除操作:删除指定位置(节点),将前一个节点的Next指针指向后一个节点,后一个节点的Prev指针指向前一个节点。 双向不循环链表的优点是可以双向遍历,可以从头节点或尾节点开始遍历;缺点是相比单链表需要更多的内存空间来存储额外的指针。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值