前言:链表在开发过程中属于出现频次十分高的一种数据结构,在java中,比如我们熟知的LinkedList、HashMap底层结构、LinkedHashMap、AQS等都使用到了链表,关于单向链表有几个经典问题???
1:如何判断链表有环
2:如果有环,找出入环的节点
3:环的长度是多少?
本篇博客就围绕这三个问题来展开讨论
目录
一:如何判断单向链表有环?
二:如果有环,找出入环的节点
三:环的长度是多少?
四:完整算法代码
五:完整测试代码
六:总结
问题一:如何判断单向链表有环?
首先我们来画一个普通的单向链表和环状链表的结构图:
可以看出在环形单向链表的EFGH形成了一个环状,那么如何用程序判断它成环呢?
这里要借助一个跑道的思想:假如有一个环形的跑道,跑道上有两个人P和Q,假设P的速度是1km/10分钟,Q的速度是2km/10分钟,速度恒定不变。如果这个跑道是环型的,他们同时出发,起初Q领先,而在某一个时刻,Q终将从后面追上过P,他们两一定会相遇,而如果是直线跑道,P和Q一定不会相遇。借助于这个思想,我们可以设置快慢指针去绕着环状链表去走,如果两个指针相遇,那么它肯定是环形的。
下面是java版的实现:通过设定两个不同速度的快慢指针来遍历整个链表,如果快慢相遇,则整个链表一定有环:
package com.baoji.lianbiao_suanfati;
/**
* 链表节点
* @author LinChi
*
*/
public 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) {
//先来判断该节点是否为null,如果为null,错误结果返回;
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(fastPointer == slowPointer) {
return true;
}
}
return false;
}
问题二:如果有环,找出入环的节点
假设环形链表的长度是L,相遇点在M,在相遇之后,只需要将fast指针指向开始的节点,然后和slow指针保持同一的速度遍历(相当于此时不分快慢,每个指针的每次步长为1),下一次两个节点相遇的时候就是链表的环形入口:关于此结论的数学证明:
/**
* 获取入口节点
* @param sourceNode 当前节点
* @return
*/
public static Node getEnterNode(Node sourceNode) {
//先来找到相遇的节点处
//先来判断该节点是否为null,如果为null,返回null;
if(sourceNode == null) {
return null;
}
//判断该节点的下一个是否有节点,如果没有下一个节点,返回null;
if(sourceNode.next == null) {
return null;
}
//慢指针
Node slowPointer = sourceNode;
//快指针
Node fastPointer = sourceNode;
while(fastPointer != null) {
//慢指针每次走一个链表格
slowPointer = slowPointer.next;
//快指针每次走两个链表格
fastPointer = fastPointer.next.next;
//当快指针和慢指针相等时,则相遇,return true;
if(fastPointer == slowPointer) {
//当找到成环位置,立即停止
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;
}
//新增节点,让该节点从环形节点处开始计数,每次步长为1,则统计出环的长度
final Node enterNode = getEnterNode(sourceNode);
//定义环的下一个指针
Node circleSecondNode = enterNode.next;
//定义环的长度
int length = 1;
//当环的下一个指针不等于成环的节点
while(circleSecondNode != enterNode) {
//长度+1
length++;
//让环的下一个指针一直向下指
circleSecondNode = circleSecondNode.next;
}
return length;
}
四.完整算法代码
package com.baoji.lianbiao_suanfati;
/**
* 链表节点
* @author LinChi
*
*/
public 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) {
//先来判断该节点是否为null,如果为null,return flase;
if(sourceNode == null) {
return false;
}
//判断该节点的下一个是否有节点,如果没有下一个节点,return flase;
if(sourceNode.next == null) {
return false;
}
//慢指针
Node slowPointer = sourceNode;
//快指针
Node fastPointer = sourceNode;
while(fastPointer != null) {
//慢指针每次走一个链表格
slowPointer = slowPointer.next;
//快指针每次走两个链表格
fastPointer = fastPointer.next.next;
//当快指针和慢指针相等时,则相遇,return true;
if(fastPointer == slowPointer) {
return true;
}
}
return false;
}
/**
* 获取入口节点
* @param sourceNode 当前节点
* @return
*/
public static Node getEnterNode(Node sourceNode) {
//先来找到相遇的节点处
//先来判断该节点是否为null,如果为null,返回null;
if(sourceNode == null) {
return null;
}
//判断该节点的下一个是否有节点,如果没有下一个节点,返回null;
if(sourceNode.next == null) {
return null;
}
//慢指针
Node slowPointer = sourceNode;
//快指针
Node fastPointer = sourceNode;
while(fastPointer != null) {
//慢指针每次走一个链表格
slowPointer = slowPointer.next;
//快指针每次走两个链表格
fastPointer = fastPointer.next.next;
//当快指针和慢指针相等时,则相遇,return true;
if(fastPointer == slowPointer) {
//当找到成环位置,立即停止
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;
}
//新增节点,让该节点从环形节点处开始计数,每次步长为1,则统计出环的长度
final Node enterNode = getEnterNode(sourceNode);
//定义环的下一个指针
Node circleSecondNode = enterNode.next;
//定义环的长度
int length = 1;
//当环的下一个指针不等于成环的节点
while(circleSecondNode != enterNode) {
//长度+1
length++;
//让环的下一个指针一直向下指
circleSecondNode = circleSecondNode.next;
}
return length;
}
}
五.测试
我们来写一个测试方法来模拟一下上面的环状节点,然后测试一下。
测试完整代码:
package com.baoji.lianbiao_suanfati;
public class Test {
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 = Node.hasCircle(node);
System.out.println("是否是环形链表:"+hasCircle);
final Node enterNode = Node.getEnterNode(node);
System.out.println("相遇节点是:"+enterNode.getValue());
final int cirCleLength = Node.getCirCleLength(node);
System.out.println("环状长度:"+cirCleLength);
}
}
程序输出如下:
六.总结
本次主要分析了环形链表的一些问题,并给出了示例代码,通过此篇博客可以学习到关于链表的一些东西,快慢指针的基本思想,以及如何求相遇节点和环的长度两个问题,如何用java求解,并熟悉链表这种数据结构,在实际学习中可以加深对环形链表的一些理解。
记得点赞+关注👉:本人github地址:github.com/Lmobject