***单链表操做之带环链表的入口点***
// 本篇博文阐述如何找到带环链表的入口点,那么,首先有必要阐述一下什么是带环链表?如何判断链表是否带环?
带环链表
:
即链表中有循环的部分,通俗的说就是没有尾节点!例如:
判断链表是否带环:
那么知道了什么是带环链表,接下来就是判断链表是否带环的判断问题了,其实也很简单,首先最简单的是判断出不
带环的链表,只要能够找到尾结点即链表不带环,那么,带环的链表怎么判断? 这里就用到前面博客讲到的快慢指针
了,定义两个指针: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");
}
画图功底比较差,找入口点这里也比较难理解,因此文字描述的比较详细;
找入口点的方法还能够用哈希表保存指针的方法,后续会补充!
讲的比较粗糙,还望谅解!