走一步的指针叫slow,走两步的叫fast。
相遇的时候,slow共移动了s步,fast共移动了2s步,这个是显而易见的。
定义a如下: 链表头移动a步到达入口点。
定义x如下: 入口点移动x步到达相遇点。
定义r如下: 从环中的某一点移动r步,又到达的这一点,也就是转了一圈的意思。
定义t如下: 从相遇点移动到入口点的移动步数
定义L如下: 从链表头移动L步,又到达了相遇点。也就是遍历完链表之后,通过最后一个节点的指针,又移动到了链表中的某一点。
其中L = a + r = a + x + t
那么slow和fast相遇了,fast必然比slow多走了n个圈,也就是 n*r 步,那么
s = a + x
2s = s + n*r , 可得 s = n*r
将s=a+x,带入s =n*r,可得 a+x = n*r, 也就是 a+x = (n-1)*r + r
从表头移动到入口点,再从入口点移动到入口点,也就是移动了整个链表的距离,即是 L = a + r , 所以r = L - a
所以 a+x = (n-1)*r + L - a , 于是 a = (n-1)*r + L - a - x = (n-1)*r + t
也就是说,从表头到入口点的距离,等于从相遇点继续遍历又到表头的距离。
所以,从表头设立一个指针,从相遇点设立一个指针,两个同时移动,必然能够在入口点相遇,这样,就求出了相遇点。
java代码实现关于带环链表的一些基本操作 大家可以自己测试
class Node<T extends Comparable<T>> implements Comparable<Node<T>>{
private T data;
private Node<T> next;
public Node(){
}
public Node(T data){
this.data=data;
this.next=null;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Node<T> getNext() {
return next;
}
public void setNext(Node<T> next) {
this.next = next;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((data == null) ? 0 : data.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Node other = (Node) obj;
if (data == null) {
if (other.data != null)
return false;
} else if (!data.equals(other.data))
return false;
return true;
}
@Override
public int compareTo(Node<T> o) {
return this.data.compareTo(o.getData());
}
}
// 判断一个单链表中是否有环: hasCycle
/** 设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,
* 如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。
* (当然,fast先行头到尾部为NULL,则为无环链表)
* */
public boolean hasCycle(){
return this.hasCycle(this.head);
}
private boolean hasCycle(Node<T> head){
boolean flag=false;
Node<T> slow=head;
Node<T> fast=head;
while(fast!=null&&fast.getNext()!=null){
slow=slow.getNext();
fast=fast.getNext().getNext();
if(slow.equals(fast)){
flag=true;
break;
}
}
return flag;
}
/*
* 找到环的入口点
* 假设单链表的总长度为L,头结点到环入口的距离为a,环入口到快慢指针相遇的结点距离为x,环的长度为r,
* 慢指针总共走了s步,则快指针走了2s步。
* 另外,快指针要追上慢指针的话快指针至少要在环里面转了一圈多(假设转了n圈加x的距离),得到以下关系:
s = a + x;
2s = a + nr + x;
=>a + x = nr;
=>a = nr - x;
*/
//寻找环的入口点
private Node<T> FindLoopPort(Node<T> head)
{
Node<T> slow=head,fast=head;
//得到相遇点
while(fast!=null && fast.getNext()!=null)
{
slow=slow.getNext();
fast=fast.getNext().getNext();
if(slow.equals(fast))
break;
}
if(fast==null||fast.getNext()==null)
return null; //无环
//slow指向开头,fast在相遇点
//得到入口点
slow=head;
while(!slow.equals(fast)){
slow=slow.getNext();
fast=fast.getNext();
}
return slow;
}
/*
* 如何知道环的长度
* 记录下问题1的碰撞点p,slow、fast从该点开始,再次碰撞所走过的操作数就是环的长度s。
* */
public int getCycleLength(){
return this.getCycleLength(this.head);
}
private int getCycleLength(Node<T> head){
Node<T> p=this.FindLoopPort(head);
if(p==null) return 0;//无环
Node<T> slow=p;
Node<T> fast=p;
int count=0;
while(fast!=null&& fast.getNext()!=null){
slow=slow.getNext();
fast=fast.getNext().getNext();
count++;
if(slow.equals(fast)){
break;
}
}
return count;
}
//带环链表的长度
public int getCycleListLength(){
return getCycleListLength(this.head);
}
private int getCycleListLength(Node<T> head){
Node<T> pnode=this.FindLoopPort(head);
if(pnode==null){
return this.size(head);
}
int count=0;//记录头结点到接入点的距离
Node<T> node=head;
while(!node.equals(pnode)){
count++;
node=node.getNext();
}
int count1=this.getCycleLength(head);//得到环的长度
return count+count1;
}