链表带环的问题研究及代码实现

145 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1. 如何判断链表是否有环?

思路是让两个指针slow和fast同时从链表头出发遍历链表,fast的速度是slow的两倍(为简单起见我们可以让slow每次走一个节点,fast每次走两个节点),若slow和fast相遇,则说明有环。

代码如下:

bool::Linked_List_with_Loop::isLoop ( )
{
	LinkNode*slow, *fast;
	
	slow = fast = first->link;
	if ( fast==NULL )
	{
		return false;
	}
	while ( fast->link&&fast->link->link )
	{
		slow = slow->link;
		fast = fast->link->link;
		if ( slow==fast )
		{
			return true;
		}
	}
}

2. 如何计算环的长度?

先给答案:第一次相遇(超一圈)时开始计数,第二次相遇时停止计数。 具体推导如下: 带环的链表示意图 如上图所示,设链表总长为L,链表头距离环的入口长度为a,环的入口点距离相遇点长度为x,环的长度为R 设slow的速度为v,fast的速度为2v。无论它们在环上哪一点相遇,它们再次相遇所需时间为R/(2v-v)=R/v。到下一次相遇时,slow走的距离刚好为R,也即得到环的长度。 代码如下:

int Linked_List_with_Loop::getLoopLength ( )
{
	LinkNode*slow, *fast;
	slow = fast = first->link;
	int loopLength = 0;
	if ( fast == NULL )
	{
		return loopLength;
	}
	bool isMeet = false;
	while ( fast->link&&fast->link->link )
	{
		
		slow = slow->link;
		fast = fast->link->link;
		if ( slow == fast )
		{
			isMeet = true;
		}
		if ( isMeet )
		{
			loopLength++;
			if ( slow->link == fast->link->link )
			{
				break;
			}
		}
	}
	return loopLength;
}

3. 如何得到环的入口点

先给答案:让两个指针分别从表头和相遇点开始走,速度一样,均为每次走一个节点,相遇时的那个点就是连接点。 带环的链表示意图 首先我们证明,当slow和fast相遇时,slow还没走完环的一圈,而fast已经走了超过一圈的路程。证明如下: 假设当slow刚到入口点时,fast已经走了nR+b(n0,,0b<Rn\ge 0,,0\le b<R,n和b不同时为0)。当b=0时,说明slow和fast在入口点相遇,这时slow显然还没走完环的一圈,fast走完了大于1的整数圈;当0<b<R0<b<R时,fast追上slow的时间为(R-b)/v,追及过程中slow走的距离为R-b,也即说明slow还没走完一圈,而fast走过的距离为2R-2b,加上之前走的nR+b,总共走完了环上的距离为(2+n)R-b>R。得证。 进一步我们可以得到相遇点距离入口点的长度为b 第一次相遇时,slow走了s=a+x步,fast走了2s=s+nR步,也即a+x=nR,也即a=(n-1)R+R-x,也即说明如果此时两个指针分别从表头和相遇点同时同速走,一定会在环的入口点相遇。 代码如下:

LinkNode * Linked_List_with_Loop::getLoopStartNode ( )
{
	LinkNode*slow, *fast;
	LinkNode * loopStartNode = NULL;
	slow = fast = first->link;
	if ( fast == NULL )
	{
		return NULL;
	}
	bool isMeet = false;
	while ( fast->link&&fast->link->link )
	{

		slow = slow->link;
		fast = fast->link->link;
		if ( slow == fast )
		{
			isMeet = true;
			break;
		}
		
	}
	if ( isMeet )
	{
		fast = first->link;
		while ( true )
		{
			slow = slow->link;
			fast = fast->link;
			if ( slow == fast )
			{
				loopStartNode = slow;
				break;
			}
		}
	}
	return loopStartNode;
}

完整源代码见: github.com/lankuohsing…

注:如无特别说明,本文中的链表均含有附加表头结点first