这段时间玩的有点多,好几周没写了,中间落下了好多内容没有总结,今天之后陆续抽时间补起来,不过学校的课也都相应的要结束,面临考试,所以速度会慢些。其余的不多说,这次先总结一下,比较常见的关于链表的相交、环的问题。
第一个是普通的环检测问题,判断一个链表是否有环,解题思路就是,定义两个指针,一个快指针一个慢指针,这两个指针在这条链表跑道跑,慢的指针速度为1,快的指针速度为2,若是这两个指针最后相遇,则说明有环,因为有环的话,快指针必然会追上慢指针,
这里我的链表结构体如下
typedef struct Node
{
int data;
struct Node *next;
}Node;
typedef Node *List;
环判断实现如下:
Node *Ring(List plist)//判断链表是否有环,返回相遇点
{
assert(plist != NULL);
Node *p = plist;//慢指针
Node *q = plist->next;//快指针
while(q->next != NULL && q != p)//当快指针走到尾结点或者两指针相遇,循环结束
{
p = p->next;//慢指针速度1
if(q->next!=NULL)//防止越界,q->next存在才进行两步的前进
{
q = q->next->next;//快指针速度2
}
}
if(q->next == NULL)//循环结束,若是快指针走到尾,则无环
{
return NULL;
}
return p;//否则返回相遇点
}
第二个问题是检测两链表是否相交,先做最简单判断,仅判断是否相交,返回bool值,因为链表相交和平常认知的相交有所区别,下边简单说明一下
如图说明,若是两个链表相交,则在交点之后一直重合,所以最简单的判断方法就是两直链表走到尾巴,判断尾巴是否相同,然后返回真假,具体实现如下
bool Banana(List plistA,List plistB)//仅判断俩无环单链表是否相交
{
assert(plistA != NULL);
assert(plistB != NULL);
Node *p1 = plistA;
Node *p2 = plistB;
while(p1->next != NULL)
{
p1 = p1->next;//找第一个尾巴
}
while(p2->next != NULL)
{
p2 = p2->next;//找第二个尾巴
}
return p1 == p2;
}
更好一点的设计就是返回交点,这个时候思路就要发生改变了,若是相交返回交点,否则返回空指针,函数名变为Node *Cross(List plistA,List plistB);,这个时候我们先假设两链表都没有环,这样若是两个链表长度不同,我们通过内部函数获得链表长度,定义两个指针初始化尾两链表头,让长的链表先走俩链表差值步数,此时的两个链表再同时走,若是链表走到尾或者走到相同的地方,则停止前进,停止后判断当前是否走到尾,走位说明不相交,否则当前停止的地方就是交点位置,返回即可,大致如图表示
代码实现如下:
Node *Cross(List plistA,List plistB)//两个无环的链表判断是否相交,返回交点地址
{
assert(plistA != NULL);
assert(plistB != NULL);
int len_a = GetLength(plistA);//获得A长度
int len_b = GetLength(plistB);//获得B长度
int t = len_a - len_b;//获得差值
Node *p = plistA;
Node *q = plistB;//初始化前进指针
if(t > 0)
{
for(int i=0;i < t;++i)
{
p = p->next;
}
}
else
{
for(int i=0;i < (-t);++i)
{
q = q->next;
}
}//长的链表先走
while(p != q && p != NULL && q != NULL)//相遇或者走到尾结束前进
{
p = p->next;
q = q->next;
}
if(p == NULL)//若是走到尾
{
return NULL;
}
return p;//否则返回交点
}
然后就是复杂于直链表的相交了,带环的链表相交,大致情况如图所示:
第一种,一个无环一个有环,则两链表必然不相交,第二、三个都有环,就要另作讨论。
以第三个为例,我们把环打开,再将链表当做普通链表处理,最后还原即可,如图
所以这次的相交判断就比较完全,实现如下:
Node *RingCross(List plistA,List plistB)//链表相交,若相交返回交点,否则返回NULL
{
assert(plistA != NULL);
assert(plistB != NULL);
Node *p = Ring(plistA);//环内获得相遇点
Node *q = Ring(plistB);//环内获得相遇点
if((p != NULL && q == NULL)||(p == NULL && q != NULL))//若是一个为环,一个非环,则不相交
{
return NULL;
}
else if(p == NULL && q == NULL)//俩个都非环,调用非环链表相交处理的函数
{
return Cross(plistA,plistB);
}
else//否则情况为两链表都有环
{
Node *p_a = p->next;//
Node *p_b = q->next;//开环准备,将开环点后的内容先保存起来
p->next = NULL;
q->next = NULL;//两环开环
Node *c = Cross(plistA,plistB);//开环后当非环链表处理
p->next = p_a;
q->next = p_b;//还原链表
return c;
}
到这里链表相交基本完成
最后是环检测的优化,我们判断链表是否有环,若有环返回入环第一个点,无环返回空指针,放在最后说是因为这里我们用到了相交的思想,若是有环,在环内相遇点处断开,断开处的后继保存,当作新的链表头,此时生成两个链表,我们获得两链表的相交点,即为原链表的入环第一个点
如图说明
这里绿色为新的B链表,黄色为新的A链表,可以看到原带环链表的入环第一个点为现在的AB链表交点,所以此时问题变成获得交点问题,所以调用之前的相交函数即可,实现如下
Node *RingFirst(List plist)//获得入环的第一个点,无环返回空指针
{
assert(plist != NULL);
Node *p1 = Ring(plist);//获得环内相遇点
if(p1 == NULL)//若是返回的点为NULL说明无环,返回结果
return NULL;
Node *p2 = p1->next;//相遇点保存
p1->next = NULL;//开环
Node *p3 = Cross(plist,p2);//找到开环后两条链表的交点
p1->next = p2;//还原链表
return p3;//返回入环第一个点
}
这里关于链表换问题,交点问题基本结束,我去休息啦~~,看完的不胜感激,本事还是菜中菜,不喜勿喷,有更棒方法交流的欢迎移步留言,感谢观众老爷们❤