如何判断单向链表是否有环?

前言:链表在开发过程中属于出现频次十分高的一种数据结构,在java中,比如我们熟知的LinkedList、HashMap底层结构、LinkedHashMap、AQS等都使用到了链表,关于单向链表有几个经典问题 :

1:如何判断链表有环  

2:如果有环,找出入环的节点

(第二个问题写一点自己的思考,所以写在前面)

方法 :先利用快慢指针(慢指针 slowPoint = slowPointer.next;快指针 fastPoint = fastPointer.next.next;)到达相遇点,然后将快指针重置为头节点,降速与慢指针一次只前进一格,再次相遇点就是入环点

自己的思考:

1.因为快指针重置后与慢指针速度一致前进,除非在入环点相遇,否则永远不相遇。因此只需证明头节点到入环点的距离 = (相遇点到入环点的距离 + n倍的环周长)

2.根据相遇前快慢指针速度不同可列等式  (L1+L2+Nf(L2+L3)) =  2 (L1+L2+Ns(L2+L3))

   根据相遇后快慢指针速度相同可列等式    L1 = L3 + ns(L2+L3)  成立

 3:环的长度是多少

目录

一:如何判断单向链表有环?

二:如果有环,找出入环的节点

三:环的长度是多少

四:测试

五:总结

问题一:如何判断单向链表有环?

首先我们来画一个普通的单向链表和环状链表的结构图:

可以看出在环形单向链表的EFGH形成了一个环状,那么如何用程序判断它成环呢?

这里要借助一个跑道的思想:假如有一个环形的跑道,跑道上有两个人P和Q,假设P的速度是1km/10分钟,Q的速度是2km/10分钟,速度恒定不变。如果这个跑道是环型的,他们同时出发,起初Q领先,而在某一个时刻,Q终将从后面追上过P,他们两一定会相遇,而如果是直线跑道,P和Q一定不会相遇。借助于这个思想,我们可以设置快慢指针去绕着环状链表去走,如果两个指针相遇,那么它肯定是环形的。

下面是java版的实现:通过设定两个不同速度的快慢指针来遍历整个链表,如果快慢相遇,则整个链表一定有环:

程序的具体实现:

  /**
     * 链表节点
     */
    public static class Node {
        private String value;
        private Node next;

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        public Node getNext() {
            return next;
        }

        public void setNext(Node next) {
            this.next = next;
        }
    } 
     /**
     * 链表是否有环
     * @param sourceNode
     * @return
     */
    public static boolean hasCircle(Node sourceNode) {
        if (sourceNode == null) {
            return false;
        }
        if (sourceNode.next == null) {
            return false;
        }
        //慢指针
        Node slowPointer = sourceNode;
        //快指针
        Node fastPointer = sourceNode;
        while (fastPointer != null) {

            //慢指针每次走一个链表格
            slowPointer = slowPointer.next;
            //快指针每次走两个链表格
            fastPointer = fastPointer.next.next;
            if (slowPointer == fastPointer) {
                return true;
            }
        }
        return false;
    }

问题二:如果有环,找出入环的节点

   假设环形链表的长度是L,相遇点在M,在相遇之后,只需要将fast指针指向开始的节点,然后和slow指针保持同一的速度遍历(相当于此时不分快慢,每个指针的每次步长为1),下一次两个节点相遇的时候就是链表的环形入口:关于此结论的数学证明:

   判断单向链表是否有环及求环入口的算法数学证明 - 知乎 

程序实现如下:

 /**
     * 获取入口节点
     * @param sourceNode
     * @return
     */
    public static Node getEnterNode(Node sourceNode) {

        if (sourceNode == null) {
            return null;
        }
        if (sourceNode.next == null) {
            return null;
        }
        //慢指针
        Node slowPointer = sourceNode;
        //快指针
        Node fastPointer = sourceNode;
        while (fastPointer != null) {
            slowPointer = slowPointer.next;
            fastPointer = fastPointer.next.next;
            if (slowPointer == fastPointer) {
                break;
            }
        }
        System.out.println("相遇点"+fastPointer.getValue());
        fastPointer = sourceNode;
        while (fastPointer != null) {
            fastPointer = fastPointer.next;
            slowPointer = slowPointer.next;
            if (fastPointer == slowPointer) {
                return fastPointer;
            }
        }
        return null;
    }

问题三:环的长度是多少?

   这个问题比较简单,既然我们已经知道了环的入口节点,只需要新增一个指针,顺着环依次循环一遍用一个变量进行累加,每次的步长设为一,然后直到和入口节点相遇(环入口的节点位置保持不变)那么环的长度也就统计出来了:

  程序具体实现:

 /**
     * 获取环的长度
     *
     * @param sourceNode
     * @return
     */
    public static int getCirCleLength(Node sourceNode) {
        if (sourceNode == null) {
            return 0;
        }
        final Node enterNode = getEnterNode(sourceNode);
        //环的下一个指针
        Node cirCleSecondNode = enterNode.next;
        int lenght = 1;
        while (cirCleSecondNode != enterNode) {
            lenght++;
            cirCleSecondNode = cirCleSecondNode.next;
        }
        return lenght;
    }

 四:测试

我们来写一个测试方法来模拟一下上面的环状节点,然后测试一下:

public static void main(String[] args) {

        final Node node = new Node();
        node.setValue("A");
        final Node node2 = new Node();
        node2.setValue("B");
        final Node node3 = new Node();
        node3.setValue("C");
        final Node node4 = new Node();
        node4.setValue("D");
        final Node node5 = new Node();
        node5.setValue("E");
        final Node node6 = new Node();
        node6.setValue("F");
        final Node node7 = new Node();
        node7.setValue("G");
        final Node node8 = new Node();
        node8.setValue("H");
        node.setNext(node2);
        node2.setNext(node3);
        node3.setNext(node4);
        node4.setNext(node5);
        node5.setNext(node6);
        node6.setNext(node7);
        node7.setNext(node8);
        node8.setNext(node5);
        final boolean hasCircle = hasCircle(node);
        System.out.println("是否是环形链表:"+hasCircle);

        final Node enterNode = getEnterNode(node);
        System.out.println("相遇节点是:"+enterNode.getValue());

        final int cirCleLength = getCirCleLength(node);
        System.out.println("环状长度:"+cirCleLength);
        
    }

程序输出如下:

 五:总结

  本次主要分析了环形链表的一些问题,并给出了示例代码,通过此篇博客可以学习到关于链表的一些东西,快慢指针的基本思想,以及如何求相遇节点和环的长度两个问题,如何用java求解,并熟悉链表这种数据结构,在实际工作中可以加深对环形链表的一些理解。

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值