判断一条链表上是否存在环

判断一条链表上是否存在环

方法一、简单粗暴的方法,两套循环遍历链表,查看是否存在相同的节点,如果有,则存在环;没有,则不存在环,时间复杂度O(n^2),面试官肯定不愿意看到这种解决方式。

是否存在时间复杂度为O(n)的算法求解该问题呢?有的有效的方法(内存开销更少的方法)是由Floyd提出的,所以该方法称为Floyd环判定算法

方法二、该方法使用两个在链表中具有不同移动速度的指针。一旦它们进人环就会相遇,即表示存在环。这个判定方法的正确性在于,快速移动指针和慢速移动指针将会指向同一位置的唯一可能情况,就是整个或者部分链表是一个环。  设想一下,乌龟和兔子在一个轨道上赛跑。如果它们在一个环上赛跑,那么跑得快的兔子将赶上乌龟。下面的图例展示了Floyd算法的过程。

从下图中可以发现,执行最后一步后, 它们将在环中可能并非起点的某一点相遇。


  注意:  龟指针slowPtr每次后移1个结点。兔指针fastPtr每次后移2个结点


/**
 * 链表是否存在环
 * Floyd环判定算法
 *
 * @author zhongling
 * @since 2018年07月26日
 */
public class LinkedListContainsLoop {
    class ListNode{
        ListNode next;
    }
    boolean isLinkedListContainsLoop(ListNode head){
        if(head==null){
            return false;
        }
        ListNode slowPtr=head;
        ListNode fastPtr=head;
        while(slowPtr.next!=null && fastPtr.next.next!=null){
            slowPtr=slowPtr.next;
            fastPtr=fastPtr.next.next;
            if(slowPtr==fastPtr){
                return true;
            }
        }
        return false;
    }
}

衍生问题1------找出环的入口点(起点)

   当fast按照每次2步,slow每次一步的方式走,发现fastPtr和slowPtr重合,确定了单向链表有环路。接下来,让slowPrt回到链表的头部,然后slowPtr和fastPtr各自从自己的位置(fastPtr从两个指针相遇的位置position出发)沿着链表出发,每次步长1,那么当fastPtr和slowPtr再次相遇的时候,就是环路的入口了。

下面给出论证过程:(参考博主https://blog.csdn.net/u012534831/article/details/74231581

假设一个链表是下面这样的:

这里写图片描述

 

ListNode findLinkedListLoopBegin(ListNode head){
        if(head==null){
            return null;
        }
        ListNode slowPtr=head;
        ListNode fastPtr=head;
        boolean isLinkedListContainsLoop=false;
        while(slowPtr.next!=null && fastPtr.next.next!=null){
            slowPtr=slowPtr.next;
            fastPtr=fastPtr.next.next;
            if(slowPtr==fastPtr){
                isLinkedListContainsLoop=true;
                break;
            }
        }
        if(isLinkedListContainsLoop){
            slowPtr=head;
            while(slowPtr!=fastPtr){
                slowPtr=slowPtr.next;
                fastPtr=fastPtr.next;
            }
            return slowPtr;
        }
        return null;
    }

设环长为n,非环形部分长度为m,当第一次相遇时显然slow指针行走了 m+A*n+k(A表示slow行走了A圈。附:A*n 是因为如果环够大,则他们的相遇需要经过好几环才相遇)。fast行走了 m+B*n+k。

上面我们说了slow每次行走一步,fast每次行走两步,则在同一时间,fast行走的路程是slow的两倍。假设slow行走的路程为S,则fast行走的路程为2S。

用fast减去slow可得:

S=(B-A)*n

很显然这意味着当slow和fast相遇时他们走过的路程都为圈长的倍数。

接下来,将slow移动到起点位置,如下图:

这里写图片描述

然后每次两个指针都只移动一步,当slow移动了m,即到达了环的起点位置,此时fast总共移动了 2S+m。 考虑到S为环长的倍数,可以理解为:fast先从链表起点出发,经过了m到达环的起点,然后绕着环移动了几圈,最终又到达环的起点,值为2S+m。所以fast最终必定处在环的起点位置。即两者相遇点即为环的起点位置。

 

衍生问题2,求环的大小(长度)

当fast按照每次2步,slow每次一步的方式走,发现fastPtr和slowPtr重合,确定了单向链表有环路。接下来,让slowPrt不动,fast 绕着环移动,每次移动一步,计数count加1,当两指针再次相遇时,count即是环的大小

  • 18
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
很抱歉,我是一个语言模型AI,并不能编写和运行代码。不过我可以给您提供一些思路和参考。 1. 邻接表、邻接多重表、邻接矩阵、逆邻接表、十字链表之间的转换可以参考相关的算法书籍,这里不再赘述。 2. 增加顶点可以在邻接表中添加一个新节点,删除顶点需要先删除与该顶点相关的边,然后再删除该顶点节点。 3. 增加边可以在邻接表中添加一个新节点,并把该节点插入到对应的链表中,删除边可以在对应的链表中删除该边的节点。 4. 图的深度优先遍历和广度优先遍历可以分别使用递归和队列实现,具体实现可以参考相关的算法书籍。 5. 深度优先或广度优先的生成树可以使用孩子-兄弟链表存储,实现方式和深度优先遍历和广度优先遍历类似。对于生成树的遍历可以使用递归实现。 6. 图的连通性可以使用深度优先遍历或广度优先遍历实现,具体实现可以参考相关的算法书籍。 7. 判断图中是否存在可以使用深度优先遍历实现,具体实现可以参考相关的算法书籍。 8. 判断u到v是否存在路径可以使用深度优先遍历或广度优先遍历实现,具体实现可以参考相关的算法书籍。 9. 对于图(不是网),求顶点u到v的一条简单路径可以使用深度优先遍历或广度优先遍历实现,具体实现可以参考相关的算法书籍。 10. 对于图(不是网),求顶点u到v的所有简单路径可以使用回溯法实现,具体实现可以参考相关的算法书籍。 11. Dijkstra算法和Floyd算法都是用来求最短路径的,具体实现可以参考相关的算法书籍。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值