单链表面试题系列之带环链表的入口点

***单链表操做之带环链表的入口点***


//  本篇博文阐述如何找到带环链表的入口点,那么,首先有必要阐述一下什么是带环链表?如何判断链表是否带环?


带环链表 即链表中有循环的部分,通俗的说就是没有尾节点!例如:

判断链表是否带环:

那么知道了什么是带环链表,接下来就是判断链表是否带环的判断问题了,其实也很简单,首先最简单的是判断出不 带环的链表,只要能够找到尾结点即链表不带环,那么,带环的链表怎么判断? 这里就用到前面博客讲到的快慢指针 了,定义两个指针:slow,fast; fast每次走两步,slow每次走一步,在链表带环的状况下,slow和fast必然会相遇,而 且相遇点必然在环内,这个不难理解吧! 既然这样,那咱们就先实现判断链表带环的代码!


@ 先统一列出链表的结构体和算法中用到的操做函数(务必仔细阅读):

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DataType;

typedef struct LinkNode
{
	DataType data;
	struct LinkNode* next;
}LinkNode,*pLinkNode;//结点结构体

typedef struct LinkList
{
	LinkNode* pHead;//头结点指针
}LinkList ,*pLinkList;//链表


void PushBack(pLinkList pList,DataType x)
{
	pLinkNode cur = NULL;
	pLinkNode pvr = NULL;
	pLinkNode newNode = (pLinkNode)malloc(sizeof(LinkNode ));
	if(newNode == NULL)
	{
		printf("out of memory\n");
		exit(0);	
	}
	assert(pList);
	cur = pList ->pHead ;
	newNode ->data = x;
	newNode ->next = NULL;

	if(cur == NULL)
	{
		pList ->pHead  = newNode ;
		return;
	}
	while(cur)
	{
		pvr = cur;
		cur = cur->next ;
	}
	pvr->next  = newNode ;

}//尾插


void InitLinkList(pLinkList pList)
{
	assert(pList);
	pList->pHead = NULL;

}//初始化列表


@ 判断链表是否带环算法:

//构造有环链表
void MakeRing(pLinkList plist1)
{
	pLinkNode cur = NULL;
	pLinkNode pvr = NULL;
	assert(plist1);

	cur = plist1 ->pHead ;

	while(cur)
	{
		pvr = cur;
		cur = cur->next ;
	}
	if(pvr != NULL)
	pvr->next  = plist1 ->pHead ;//找到尾结点,让它能够指向前面的结点!
}

//判断链表是否带环
int Judge_Ring(pLinkList pList)
{
	pLinkNode slow = NULL;
	pLinkNode fast = NULL;

	assert(pList);

	slow = fast = pList ->pHead ;//刚开始都指向第一个结点;

	while(fast && fast->next)
	{
		slow = slow->next ;//一次走一步
		fast = fast->next ->next ;//一次走两步
		if(slow == fast)//若是相遇则直接返回1,表明有环;
			return 1;
	}
	return 0;//不然返回0,表明无环;
}

void test3()
{
	LinkList List1 ;//本身建立一个链表,方便测试;
	int ret = 0;

	InitLinkList(&List1);//初始化链表
    PushBack(&List1, 2);//尾插
	PushBack(&List1, 2);
	PushBack(&List1, 2);
	MakeRing(&List1);
	ret =  Judge_Ring (&List1);//接收函数返回值并进行判断;
	if(ret == 1)
		printf("yes\n");
	else
		printf("no\n");
}

int main()
{
	test3();
	system("pause");
	return 0;
}


@ 既然已经会判断链表是否带环了,接下来就得找找这环的入口点,所谓入口点,就是环的开始的那个结点;如今如

何去找这个结点呢? 我再这里将推理过程列了出来,由于画工太差,就只有文字描述了;


@ 其实,仔细想一想的话,无非就是从表头到入口点距离为 a,当知道相遇点后,从表头开始一个指针start,而要经过环内指针找入口点,都是一次走一步,则环内指针要与start在入口点相遇,那么最起码环内指针走的距离也是a,这样的话,方向就明确了,只要找到a的关系捋一下, S = a + x;   S = nr; 则 a + x  = nr;而指针此时开始走确定是走的是 t 这段距离,那么看看可不能够找到 a 和 t 的关系,既然环的一圈是r, 那么 x+t = r,没问题吧;则前面的 a + x =nr,是否是能够变为 a = (n - 1)r + t;  即从环内指针从 t 开始走,走过 n - 1 圈后,和start从表头开始走,直到相遇时,走过的距离都是 a(注意:a表示的是从表头到入口点的距离);即start和环内指针相遇时必然是入口点!


代码实现:

//有环则找出入口点
pLinkNode  FindEntry(pLinkList pList)
{
	pLinkNode start = NULL;//从表头开始的指针;
	pLinkNode slow = NULL;//快慢指针判断是否有环
	pLinkNode fast = NULL;
	pLinkNode meet = NULL;//用来表示相遇点的指针;

	assert(pList);

	//找相遇点指针的和判断是否有环的部分同样;
	slow = fast = pList ->pHead ;

	while(fast && fast->next)
	{
		slow = slow->next ;
		fast = fast->next ->next ;
		if(slow == fast)
			break;
	}
	meet = slow;//用meet来存放相遇点;

	if(fast == NULL || fast->next == NULL)
		return NULL;

	start = pList ->pHead ;

	while(start != meet)
	{
		start = start->next ;
		meet = meet->next ;
	}
	return start;//最后返回入口点;
}

void test4()
{
	LinkList List1 ;
	pLinkNode tmp = NULL;
	int ret = 0;

	InitLinkList(&List1);
    PushBack(&List1, 1);
	PushBack(&List1, 2);
	PushBack(&List1, 3);
	PushBack(&List1, 4);
	PushBack(&List1, 5);
	PushBack(&List1, 6);
	MakeRing(&List1);
	tmp = FindEntry (&List1);
	if(tmp != NULL)
		printf("%d\n", tmp->data );
	else
		printf("error!\n");
}

画图功底比较差,找入口点这里也比较难理解,因此文字描述的比较详细;

找入口点的方法还能够用哈希表保存指针的方法,后续会补充!

讲的比较粗糙,还望谅解!