问题:如何判断一个单向链表中是否存在环?
例如:
链表中存在环(B-->D):
<-- <--^
| |
v |
A-->B-->C-->D
链表中不存在环:
A-->B-->C-->D-->E-->F
解题思路:
从一个实际的生活场景出发,两个人,在一个环形的操场上跑步的时候,如果有一个人跑得比另一个人还要快,那么,在n圈之后,这两个人总会在操场上的某个点相遇。将操场类比于链表中存在的环路径,将两个人看成两个指针,那么这道题的解题思路就自然而然的出来了。
具体步骤如下:
- 初始化两个指针a,b,同时指向链表的开头
- a指针走一步,b指针走两步(我们姑且将a指针称为慢指针,将b指针称为快指针)
- 重复步骤2,直到b指针无法继续往下走两步或者a,b指针相遇
- 当b指针无法继续往下走两步的时候,说明链表中不存在环,b指针即将走到链表的末尾端。
- 当a,b指针相遇,说明链表中存在环,因为只有存在环的情况,a,b指针才有可能会在环中的某个点相遇
具体代码如下:
/**
* @author 学徒
*
* 用于判断链表中是否存在环
*
*/
public class CycleLinkedList {
/**
* 循环链表中的节点类
*/
static class Node<T>{
//节点值
T value;
//节点的下一个节点的指针
Node<T> next;
public Node(T value){
this(value,null);
}
public Node(T value,Node next){
this.next=next;
this.value=value;
}
}
/**
* 用于判断该链表中是否存在着环
* @param head 链表的头节点
* 当存在环时,返回true,否则返回false
*/
public boolean judge(Node head) {
if(head==null){
return false;
}
//两个指向头结点的指针
Node a=head,b=head;
while(true){
if(b.next==null||b.next.next==null){
return false;
}
a=a.next;
b=b.next.next;
if(a==b){
return true;
}
}
}
public static void main(String[] args){
Node<String> a=new Node<String>("A");
Node<String> b=new Node<String>("B");
Node<String> c=new Node<String>("C");
Node<String> d=new Node<String>("D");
a.next=b;
b.next=c;
c.next=d;
d.next=b;
CycleLinkedList list=new CycleLinkedList();
boolean result=list.judge(a);
System.out.println("链表中环的结果:"+result);
}
}
从上面的链表中是否存在环的问题,可以延伸出与链表中是否存在环相关的另一个问题
问题: 链表中构成环的元素的个数应该如何计算?
对于这个问题,我们稍微沿着解决上面的链表存在环的问题的思路继续往下想,当链表中存在环的时候,快慢指针相遇,那么这个时候,我们只需要让快指针停留在相遇的位置,让慢指针再次走一遍,边走边记录步数,当快慢指针再次相遇的时候,慢指针所走的步数,便是构成环的链表的环中元素个数。
我们只需要稍微修改下上面的代码即可,具体代码如下:
/**
* @author 学徒
*
* 用于判断链表中是否存在环
*
*/
public class CycleLinkedList {
/**
* 循环链表中的节点类
*/
static class Node<T>{
//节点值
T value;
//节点的下一个节点的指针
Node<T> next;
public Node(T value){
this(value,null);
}
public Node(T value,Node next){
this.next=next;
this.value=value;
}
}
/**
* 用于判断该链表中是否存在着环
* @param head 链表的头节点
* 当存在环时,返回环中元素个数,否则返回0
*/
public int judge(Node head) {
if(head==null) {
return 0;
}
//两个指向头结点的指针
Node a=head,b=head;
while(true){
//当出现该情况的时候,说明无环
if(b.next==null||b.next.next==null){
return 0;
}
a=a.next;
b=b.next.next;
//当其存在环
if(a==b){
int number=1;
a=a.next;
while(a!=b){
a=a.next;
number++;
}
return number;
}
}
}
public static void main(String[] args){
Node<String> a=new Node<String>("A");
Node<String> b=new Node<String>("B");
Node<String> c=new Node<String>("C");
Node<String> d=new Node<String>("D");
a.next=b;
b.next=c;
c.next=d;
d.next=b;
CycleLinkedList list=new CycleLinkedList();
int result=list.judge(a);
System.out.println(result);
}
}
顺着上面的问题继续的往下想,我们可以延伸出另一个问题
问题:我们是否可以得到第一个进入该链表的环的节点的元素?
对于该问题,我们可以通过以下的方式得到该节点。
- 将快指针重新指向链表的头节点
- 快指针和慢指针同时走,当快指针和慢指针相遇时,该节点便是链表中第一个进入环的节点
ps:以上只是一个结论的步骤总结。实际上,可以通过分析得到,在环中,快慢指针第一次相遇时的节点位置与进入环的第一个节点的顺时针方向的距离同链表头节点到进入环中第一个节点的位置的距离相等。
具体代码如下:
/**
* @author 学徒
*
* 用于判断链表中是否存在环
*
*/
public class CycleLinkedList {
/**
* 循环链表中的节点类
*/
static class Node<T>{
//节点值
T value;
//节点的下一个节点的指针
Node<T> next;
public Node(T value){
this(value,null);
}
public Node(T value,Node next){
this.next=next;
this.value=value;
}
}
/**
* 用于判断该链表中是否存在着环
* @param head 链表的头节点
* 当存在环时,返回环中元素个数,否则返回0
*/
public Node judge(Node head) {
if(head==null) {
return null;
}
//两个指向头结点的指针
Node a=head,b=head;
while(true){
//当出现该情况的时候,说明无环
if(b.next==null||b.next.next==null){
return null;
}
a=a.next;
b=b.next.next;
//当其存在环
if(a==b){
b=head;
while(a!=b){
a=a.next;
b=b.next;
}
return b;
}
}
}
public static void main(String[] args){
Node<String> a=new Node<String>("A");
Node<String> b=new Node<String>("B");
Node<String> c=new Node<String>("C");
Node<String> d=new Node<String>("D");
a.next=b;
b.next=c;
c.next=d;
d.next=b;
CycleLinkedList list=new CycleLinkedList();
Node result=list.judge(a);
System.out.println(result.value);
}
}
主目录: