一:单链表——⑤带环单链表的详细讲解

  今天看了一篇关于带环单链表精讲的文章,在这里给大家做一个总结。之前看过很多有关单链表带环的文章,但是有些文章讲的太文章化,不容易理解,理论性太强。接下来我会用最简单通俗易懂的语言解析这个问题。

  当你拿到一个单链表的数据信息时,我相信大部分的人都会选择去判断一下这个单链表是否有环?这也是最基本的反应。下面我给出单链表的数据结构:


typedef int Elemtype;

//头节点中的data域存放是链表的长度

typedef struct ListNode

{

   Elemtype data;//值域

   ListNode *m_pNext;//指针域

}ListNode,*PListNode;

链表的具体操作这里就不详细列出了,在我的前几篇博客中都有详细的代码。

一:那么我们接下来分析,如何判断一个链表是否有环呢?

       我们采用假设的方法,我们先假设该链表就是一个带环的单链表,那么我们来分析:

  有的同学会想到说,既然这个链表是带环的,那么从头开始遍历链表,直至找到某两个节点的地址一样,或者遍历到NULL。这是脑子直的人的想法也是最最简单易懂的方法 。

  在单链表的很多处理的过程中我们都会采用一种很有效的处理方式:辅助指针(快慢指针),就像古人说的三个臭皮匠顶过一个诸葛亮。我们让快指针每次走两个单位,慢指针每次走一个单位,我们可以发现,当fast != NULL&&fast->next !=NULL,时只要fast = slow那么这个链表就是带环的,反之不满足上述条件,就是不带环的链表。

//函数名:Isloop
//函数功能:判断是不是带环的链表
//函数参数:链表的头节点指针

bool Isloop(PListNode plist)//plist 为头结点
{
	if(plist == NULL) return true;//首先判断传入的指针是否存在
	PListNode pf = plist;//快指针
	PListNode ql = plist;//慢指针

	while(pf != NULL && pf->m_pNext!= NULL)//说明没有走到尾
	{
		pf = pf->m_pNext->m_pNext;//当出现二级指向时,我们要判断pf->my_pNext不为空
		ql = ql->m_pNext;
		if(pf == ql)
		{
			break;
		}
	}//存在两种情况,①pf或者pf->m_pNext为空②pf=ql
	//然后下面的代码就是对这两种情况的解释
	if(pf == NULL || pf->m_pNext == NULL)
	{
		return false;
	}
	else
	{
		return true;
	}
}

  

我相信大部分的同学,在判断完成后,肯定心里窃窃欢喜,便把这道题目抛掷脑后了。当时对于初学者的作者来说也是这样,但是后来作者又想了几个值得思考的问题

二:既然我们确定这是个带环的链表那么这个链表的入口点在哪里呢?怎样确定这个入口点呢?

       当初我查阅了很多资料,一直以为这是一个很深奥的数学公式推导问题,并且很多资料也就是朝这方面解释的。但是我问了自己的C++老师他给出了最简单易懂的算法:

首先我们来看最简单的一种情况:

首先我们可以很确定的一件事:那就是fast和slow走的步数一样,但是由于fast一次走两步,所以fast走过的长度是slow的两倍。

同时我们模拟出最简单的情况:我们假设不在环的直线部分是很短的(因为这里有一个临界值)那样的话fast只在环中走了一圈,蓝色就是slow所走的路径,绿色就是fast所走的路径,同时呢在下面那张图中红色部分就是fast比slow多走 的那部分。

同时呢由于len fast = 2 * len slow,所以呢红色部分和slow的蓝色路径一样

同时呢我们发现他们有共同的部分,去掉共同部分。我们得到一个很重要的信息:

  从起点到入口点   和   相遇点到入口点  的长度一样,这就是我们的算法。

还有一种普通的情况那就是,当直线部分很长,超过了那个临界点。此时fast比slow在环内多走了n圈,但是上面的理论对于这种普通的情况仍然使用,只不过是fast在环内走了很多圈。

代码实现;

//环的入口点

//函数名:FindEtrance
//函数功能:找到环的入口点并且返回该节点的指针
//函数返回值:节点地址

PListNode FindEtrance(PListNode plist)
{
	PListNode fast = plist;//快指针
	PListNode slow = plist;//慢指针
	while(fast != NULL && fast->m_pNext != NULL)
	{
		fast = fast->m_pNext->m_pNext;
		slow = slow->m_pNext;
		if(fast == slow)
		{
			break;
		}
	}
	//分为两种情况
	if(fast == NULL && fast->m_pNext == NULL)
	{
		return NULL;
	}
	//是环的前提下,此时快指针已经追上慢指针
	slow = plist;//从此时开始快慢各走一步下次相遇即是环的入口点
	while(slow != fast)
	{
		fast = fast->m_pNext;
		slow = slow->m_pNext;
	}
	return slow;
}

三:既然我们现在也知道了环的入口点,那么还有什么可以探寻的呢?

      我们想要知道环的长度:

      算法分析:当fast和slow第一次在相遇点相遇时,此后他们人生的道路只能在那个环里度过了,同时我们还可以得到一个结论 

那就是,相对慢指针来说,他从相遇点,到下一次相遇时刚好慢指针走了环的长度,

这就是解决此问题的算法:

代码如下:

//计算环的长度,第一次相遇开始计数,第二次停止计数


//函数名:Length
//函数功能:计算链表环的长度
//函数返回值:环的长度

int length(PListNode plist)
{
	PListNode fast = plist;//快
	PListNode slow = plist;//慢

	int length = 0;
	bool begin = false;
	bool again = false;

	while(fast != NULL && fast->m_pNext != NULL)
	{
		fast = fast->m_pNext->m_pNext;
		slow = slow->m_pNext;
		if(fast == slow && again == true)
		{
			break;
		}
		if(fast == slow && again == false)
		{
			begin = true;
			again = true;
		}
		//第一次到第二次相遇,通过慢指针来计数
		if(begin == true)
		{
			++length;
		}
	}
	return length;
}

到现在,我整理的单链表的内容已经基本完整了。

如果有什么错误或者意见希望大家多多提出。

猜你喜欢

转载自blog.csdn.net/genzld/article/details/81346669