当前位置: 首页 > news >正文

ZT:判断链表是否有环以及环的入口点

解法:
1. 遍历链表,将已经遍历过的节点放在一个hash表中,如果一个节点已经存在hash表中,说明有环。时间:O(n) 空间:O(n)
2. 反转链表 时间O(n),空间O(1),使用三个指针
3. 快慢指针。 时间O(n), 空间O(1),使用两个指针

参考:
http://kb.cnblogs.com/page/52054/
http://www.cnblogs.com/shawn-zhou/archive/2008/11/26/1341307.html
http://kb.cnblogs.com/page/52054/
http://keep.javaeye.com/blog/293454

【摘要】有一个单链表,其中可能有一个环,也就是某个节点的next指向的是链表中在它之前的节点,这样在链表的尾部形成一环。1、如何判断一个链表是不是这类链表?2、如果链表为存在环,如果找到环的入口点?扩展:判断两个单链表是否相交,如果相交,给出相交的第一个点。

有一个单链表,其中可能有一个环,也就是某个节点的next指向的是链表中在它之前的节点,这样在链表的尾部形成一环。

问题:

1、如何判断一个链表是不是这类链表?
2、如果链表为存在环,如果找到环的入口点?

解答:

一、判断链表是否存在环,办法为:

设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表)程序如下:

 

ExpandedBlockStart.gif 判断环是否存在
 1  bool  IsExitsLoop(slist  *  head)
 2  {
 3      slist  *  slow  =  head ,   *  fast  =  head;
 4 
 5        while   ( fast   &&   fast  ->  next ) 
 6       {
 7           slow   =   slow  ->  next;
 8           fast   =   fast  ->  next  ->  next;
 9           if   ( slow   ==   fast )   break  ;
10       }
11   
12        return     !  (fast   ==   NULL   ||   fast  ->  next   ==   NULL);
13 

 

 

二、找到环的入口点

当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(1<=n)。假设slow走了s步,则 fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:

2s = s + nr
s= nr

设整个链表长L,入口环与相遇点距离为x,起点到环入口点的距离为a。
a + x = nr
a + x = (n – 1)r +r = (n-1)r + L - a
a = (n-1)r + (L – a – x)

(L – a – x)为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点,于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。

程序描述如下:

ExpandedBlockStart.gif 找到环的入口点
 1  slist  *   FindLoopPort(slist  *  head)
 2  {
 3      slist  *  slow   =   head,   *  fast   =   head;
 4 
 5       while   ( fast   &&   fast  ->  next ) 
 6      {
 7          slow   =   slow  ->  next;
 8          fast   =   fast  ->  next  ->  next;
 9           if   ( slow   ==   fast )   break  ;
10      }
11 
12       if   (fast   ==   NULL   ||   fast  ->  next   ==   NULL)
13           return   NULL;
14 
15      slow   =   head;
16       while   (slow   !=   fast)
17      {
18           slow   =   slow  ->  next;
19           fast   =   fast  ->  next;
20      }
21 
22       return   slow;
23 

 

附一种易于理解的解释:

一种O(n)的办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,如果有环的话两者必然重合,反之亦然):
关于这个解法最形象的比喻就是在操场当中跑步,速度快的会把速度慢的扣圈

可以证明,p2追赶上p1的时候,p1一定还没有走完一遍环路,p2也不会跨越p1多圈才追上

我们可以从p2和p1的位置差距来证明,p2一定会赶上p1但是不会跳过p1的

因为p2每次走2步,而p1走一步,所以他们之间的差距是一步一步的缩小,4,3,2,1,0 到0的时候就重合了

根据这个方式,可以证明,p2每次走三步以上,并不总能加快检测的速度,反而有可能判别不出有环

既然能够判断出是否是有环路,那改如何找到这个环路的入口呢?

解法如下: 当p2按照每次2步,p1每次一步的方式走,发现p2和p1重合,确定了单向链表有环路了

接下来,让p2回到链表的头部,重新走,每次步长不是走2了,而是走1,那么当p1和p2再次相遇的时候,就是环路的入口了。

这点可以证明的:

在p2和p1第一次相遇的时候,假定p1走了n步骤,环路的入口是在p步的时候经过的,那么有

p1走的路径: p+c = n;         c为p1和p2相交点,距离环路入口的距离

p2走的路径: p+c+k*L = 2*n;   L为环路的周长,k是整数

显然,如果从p+c点开始,p1再走n步骤的话,还可以回到p+c这个点

同时p2从头开始走的话,经过n步,也会达到p+c这点

显然在这个步骤当中p1和p2只有前p步骤走的路径不同,所以当p1和p2再次重合的时候,必然是在链表的环路入口点上。

 

扩展问题:

判断两个单链表是否相交,如果相交,给出相交的第一个点(两个链表都不存在环)。

比较好的方法有两个:

一、将其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在,则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。

二、如果两个链表相交,那个两个链表从相交点到链表结束都是相同的节点,我们可以先遍历一个链表,直到尾部,再遍历另外一个链表,如果也可以走到同样的结尾点,则两个链表相交。

这时我们记下两个链表length,再遍历一次,长链表节点先出发前进(lengthMax-lengthMin)步,之后两个链表同时前进,每次一步,相遇的第一点即为两个链表相交的第一个点。

 

转载于:https://www.cnblogs.com/missair/archive/2010/08/05/1793492.html

相关文章:

  • mfs 测试实验--环境搭建
  • linux 入门学习
  • 报ERROR: Fast Data Access MMU Miss 错误解决思路
  • ASP.NET获取当前网址方法
  • 如果你已经20岁了,你真的输不起了,别再孩子了.....
  • SQL的语法和规则
  • 漫谈VoIP技术 H.323与SIP比较分析
  • FreeBSD 6.0架设管理与应用-第二章 安裝 FreeBSD
  • MyIsam 锁
  • Eqs--POJ 1840
  • gui2exe
  • NeHe OpenGL第三十九课:物理模拟
  • 珀耳帖效应
  • 主机与显示器不兼容故障分析排除
  • socket网络编程常用的结构及函数小结
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • 【笔记】你不知道的JS读书笔记——Promise
  • iOS | NSProxy
  • JavaScript新鲜事·第5期
  • Java多线程(4):使用线程池执行定时任务
  • nginx(二):进阶配置介绍--rewrite用法,压缩,https虚拟主机等
  • PHP的Ev教程三(Periodic watcher)
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • ucore操作系统实验笔记 - 重新理解中断
  • WordPress 获取当前文章下的所有附件/获取指定ID文章的附件(图片、文件、视频)...
  • 不上全站https的网站你们就等着被恶心死吧
  • 前端自动化解决方案
  • 如何在GitHub上创建个人博客
  • 适配iPhoneX、iPhoneXs、iPhoneXs Max、iPhoneXr 屏幕尺寸及安全区域
  • 详解NodeJs流之一
  • 一道闭包题引发的思考
  • 转载:[译] 内容加速黑科技趣谈
  • mysql面试题分组并合并列
  • 进程与线程(三)——进程/线程间通信
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • ​批处理文件中的errorlevel用法
  • # Python csv、xlsx、json、二进制(MP3) 文件读写基本使用
  • #每日一题合集#牛客JZ23-JZ33
  • (22)C#传智:复习,多态虚方法抽象类接口,静态类,String与StringBuilder,集合泛型List与Dictionary,文件类,结构与类的区别
  • (JSP)EL——优化登录界面,获取对象,获取数据
  • (非本人原创)我们工作到底是为了什么?​——HP大中华区总裁孙振耀退休感言(r4笔记第60天)...
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (附源码)计算机毕业设计SSM疫情社区管理系统
  • (介绍与使用)物联网NodeMCUESP8266(ESP-12F)连接新版onenet mqtt协议实现上传数据(温湿度)和下发指令(控制LED灯)
  • (六)Hibernate的二级缓存
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (深入.Net平台的软件系统分层开发).第一章.上机练习.20170424
  • (四)Controller接口控制器详解(三)
  • (一)appium-desktop定位元素原理
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .NET 简介:跨平台、开源、高性能的开发平台
  • .NET开源项目介绍及资源推荐:数据持久层 (微软MVP写作)