链表-单链表的环

一、如何检查链表中是否有环
方法一:从头结点开始遍历链表,把每次访问到的结点(或其地址)存入一个集合(hashset)或字典(dictionary),如果发现某个结点已经被访问过了,就表示这个链表存在环,并且这个结点就是环的入口点。这需要O(N)空间和O(N)时间,其中N是链表中结点的数目。
	/**
	 * 判断是否有环
	 */
	public boolean isLoop(Node head){
		Set<Node> set = new HashSet<Node>();
		while(head!=null){
			if(set.contains(head)){
				return true;
			}else{
				set.add(head);
			}
			head=head.next;
		}
		return false;
	}


方法二:定义两个指针fast与slow,fast是快指针,slow是慢指针,二者的初始值都指向链表头,slow每次前进一步,fast每次前进2步,两个指针同时向前移动。快指针每走一步都要跟慢指针比较,直到当快指针等于慢指针为止,这就证明这个链表是带环的单向链表,否则,证明这个链表是不带环的(fast先行到达尾部为null,则为无环链表)。
	/**
	 * 判断是否有环
	 */
	public boolean isLoop(Node head){
		Node fast = head;
		Node slow = head;
		
		while(fast!=null && fast.next!=null){
			fast = fast.next.next;
			slow = slow.next;
			if(fast==slow){
				return true;
			}
		}
		return false;
	}

这个方法需要O(1)空间和O(N)时间,优于第一种方法。

二、环长
根据上述的分析,以下都采用快慢指针的方式。
快慢指针第一次相遇之后,快指针停止不动,慢指针继续前行,再次相遇时慢指针的步长就是环长。
	/**
	 * 获得环长
	 */
	public int getLoopLength(Node head){
		Node fast = head;
		Node slow = head;
		
		// 第一次相遇
		while(fast!=null && fast.next!=null){
			fast = fast.next.next;
			slow = slow.next;
			if(fast==slow){
				break;
			}
		}
		
		int loopLength = 0;
		// 第二次相遇
		while(slow!=null){
			slow = slow.next;
			loopLength++;
			if(fast==slow){
				break;
			}
		}
		
		return loopLength;
	}


三、子链长、环的入口
假设链表总长S,子链长s1,环长L,S=L+s1。
定义两个慢指针P1和P2(每次前行一步),P1先行L步,然后P1和P2同时移动,两个指针相遇时,P2的步长就是子链长,相遇的结点就是环的入口结点。

	/**
	 * 获得入口结点
	 */
	public Node getLoopPort(Node head){
		Node p1=head;
		Node p2=head;
		int loopLength = getLoopLength(head);
		
		// p1前行L步(L是环长)
		for(int i=1;i<=loopLength;i++){
			p1=p1.next;
		}
		
		int childLength=1; // 假设子链长度包括入口结点
		while(p1!=p2){
			p1=p1.next;
			p2=p2.next;
			childLength++;
		}
		
		return p1;
	}

以上方法需要先取得环的长度,下面介绍一种代码更简洁的方法,但是理解起来有点绕:
1)还是快慢指针的方式,p1每次前进一步,p2每次前进2步,快慢指针第一次相遇在环内的X结点;
2)指针p2移动到链表的头结点;
3)p1和p2同时移动,p1和p2每次移动一步,p1和p2将再次相遇在环的入口。
代码实现:
	/**
	 * 获得入口结点
	 */
	public Node getLoopPort(Node head){
		Node p1=head; //起初是慢指针
		Node p2=head; //起初是快指针
		
		//第一次相遇
		while(p2!=null&&p2.next!=null){
			p2 = p2.next.next;
			p1 = p1.next;
			if(p1==p2){
				break;
			}
		}
		
		//没有环则返回空
		if(p2==null || p2.next==null){
			return null;
		}
		
		// p2回到头结点
		p2=head;
		
		int childLength=1; // 子链长
		
		//p1和p2每次一步,再次相遇在环的入口
		while(p1!=p2){
			p1=p1.next;
			p2=p2.next;
			childLength++;
		}
		
		return p1;
	}


逻辑说明:
假设快慢指针的相遇点距离入口结点X步,环长L,子链长s1,链表总长S,然后两个指针每次一步向前移动,如果它们在入口点相遇,就要证明nL-X=s1(n>=1).
>快慢指针第一次相遇的时候,慢指针必定没有遍历完整个列表,如果慢指针走了s步,那么快指针走了2s步,此时快指针必定在环内循环了n圈,所以满足如下关系式:
2s=s+nL
s=nL
X+s1=nL
s1=nL-X



猜你喜欢

转载自margaret0071.iteye.com/blog/2354107