微软亚院之编程判断俩个链表是否相交
给出俩个单向链表的头指针,比如h1,h2,判断这俩个链表是否相交。
为了简化问题,我们假设俩个链表均不带环。
问题扩展:
1.如果链表可能有环列?
2.如果需要求出俩个链表相交的第一节点列?
在解此题前,先判断一个单链表是否有环及环的链接点。参考:http://blog.sina.com.cn/s/blog_725dd1010100tqwp.html
给定一个单链表,只给出头指针h:
1)如何判断是否存在环?
2)如何知道环的长度r?
3)如何找出环的链接点即环入口在哪里?
4)带环链表的长度是多少,即a+r是多少?
解法:
假设存在环,且环长设为r,从头结点到环的入口长度为a;设定两个指针slow和fast,从头指针开始,每次分别前进1步、2步,设在P点两个指针相碰撞,碰撞点距环入口长度为X;则可知
slow走了S步:S=a+mr+x;
fast走了2S步:2S=a+nr+x;
则可知S=(n-m)r,即fast指针和slow指针相遇时,fast指针比slow指针多走了(n-m)r步;
即a+x=nr;将两指针中的其中一个放到头指针head处,fast指针和slow指针开始一步一步的走,其中从头结点的指针走了a步,走到环入口O点,另一个第二指针从P点开始走,也走了a步,则第二指针距环入口共有x+a步,即nr步,则也走到了环入口,即当第一次碰撞点后,把其中一个指针放到头结点,而另一个一步一步走,当在相遇时,即为环入口O,且走了多少步即为a。
引用http://blog.csdn.net/thestoryofsnow/article/details/6822576里的,个人觉得这个的解释很好,读起来更好理解,语言也精练。
问题:如何检测一个链表是否有环,如果有,那么如何确定环的起点.
龟兔解法的基本思想可以用我们跑步的例子来解释,如果两个人同时出发,如果赛道有环,那么快的一方总能追上慢的一方。进一步想,追上时快的一方肯定比慢的一方多跑了几圈,即多跑的路的长度是圈的长度的倍数。
基于上面的想法,Floyd用两个指针,一个慢指针(龟)每次前进一步,快指针(兔)指针每次前进两步(两步或多步效果是等价的,只要一个比另一个快就行,从后面的讨论我们可以看出这一点)。如果两者在链表头以外的某一点相遇(即相等)了,那么说明链表有环,否则,如果(快指针)到达了链表的结尾,那么说明没环。
环的检测从上面的解释理解起来应该没有问题。接下来我们来看一下如何确定环的起点,这也是Floyd解法的第二部分。方法是将慢指针(或快指针)移到链表起点,两者同时移动,每次移动一步,那么两者相遇的地方就是环的起点。
这样做的道理用下图解释。假设起点到环的起点距离为m,已经确定有环,环的周长为n,(第一次)相遇点距离环的起点的距离是k。那么当两者相遇时,慢指针移动的总距离为i,i = m + a * n + k,因为快指针移动速度为慢指针的两倍,那么快指针的移动距离为2i,2i = m + b * n + k。其中,a和b分别为慢指针和快指针在第一次相遇时转过的圈数。我们让两者相减(快减慢),那么有i = (b - a) * n。即i是圈长度的倍数。利用这个结论我们就可以理解Floyd解法为什么能确定环的起点。将一个指针移到链表起点,另一个指针不变,即距离链表起点为i处,两者同时移动,每次移动一步。当第一个指针前进了m,即到达环起点时,另一个指针距离链表起点为i + m。考虑到i为圈长度的倍数,可以理解为指针从链表起点出发,走到环起点,然后绕环转了几圈,所以第二个指针也必然在环的起点。即两者相遇点就是环的起点。
引用结束,回到之前的分析:
1)对于问题1,使用追赶的方法,设定两个指针slow和fast;初始slow=head; fast=head;之后slow=slow->next; fast=fast->next->next;;即从头指针开始,每次分别前进1步、2步。如果存在环,则两者相遇;如不存在环,fast遇到NULL退出。
2)对于问题2,恢复快、慢指针,从环入口开始,当再次相遇时,slow指针走的步数即为环长r。或者快、慢指针都在环入口,但只有慢指针走,而快指针不动,则两者再相遇时走的步数即为环长r。
3)在碰撞点P处,一个指针从头结点走,一个在P点走,当再次相遇时即为环入口。且环入口到头结点的距离a即为走的步数,因为再相遇,从头结点走的指针到环入口走了a步,另一个指针相对头结点,没开始走时,距头结点为a+mr+X步,即S步,为r的倍数,又共同走了a步,则距头结点为S+a步,则两者相遇时,之间相差S,r的倍数,则再相遇时在环入口处;且走的a步即为头结点到环入口的距离。
4)链表的长度即为a+r,由上面可知a和r,则即可得。
在来看链表是否相交的问题:
给出俩个单向链表的头指针,比如h1,h2,判断这俩个链表是否相交。
为了简化问题,我们假设俩个链表均不带环。
问题扩展:
1.如果链表可能有环列?
2.如果需要求出俩个链表相交的第一节点列?
思路:1.首先假定链表不带环
那么,我们只要判断俩个链表的尾指针是否相等。
相等,则链表相交;否则,链表不相交。
2.如果链表带环,
那判断一链表上俩指针相遇的那个节点,在不在另一条链表上。
如果在,则相交,如果不在,则不相交。
所以,事实上,这个问题就转化成了:
1.先判断带不带环
2.如果都不带环,就判断尾节点是否相等
3.如果都带环,判断一链表上俩指针相遇的那个节点,在不在另一条链表上。
如果在,则相交,如果不在,则不相交。
不存在一个带环,一个不带环,如是这样的话,一定不相交,否则相违背,假设相交的话,则另一个只定也有环。
单个链表是否为环及环的长度、入口等代码如下:
#include<stdio.h>
#include<stdlib.h>
int key;//指定的环结点位置对应的数字
struct LinkList
{
int data;
LinkList *next;
};
struct Node
{
LinkList *head;
LinkList *meet; //fast和slow第一次相遇的图中P点
bool loop; //是否是环
int len; //输入链表的长度 len=a+r
int a; //头指针到环入口的距离 a
int r; //环的长度 r
int key; //人工设定的环入口值key
LinkList *circ; //人工输入链表中的环节点o指向的指针
LinkList *o; //对链表进行查找时找到的环节点指针,理论上应和incirc相等
}*L1,*L2;
void initNode(Node *&L)
{
//初始化Node结点。
L=(Node*)malloc(sizeof(Node));;
L->head=NULL;
L->loop=false;
L->a=0;
L->r=0;
L->key=0;
L->circ=NULL;
L->o=NULL;
L->meet=NULL;
//初始化Node中的LinkList结点
LinkList *p,*q;int i;
printf("请输入L->len个数和人工设定的环入口值L->key\n");
scanf("%d %d",&L->len,&L->key);
for (i=1; i<=L->len; i++)
{
p=(LinkList *)malloc(sizeof(LinkList));
if (p==NULL) printf("申请空\n");
scanf("%d",&p->data);
p->next=NULL;
if (i==1)
{
L->head=p;
q=p;
}
else
{
q->next=p;
q=p;
}
if (L->key==p->data)
{
L->circ=p;
}
}
if(L->circ==NULL)
{
printf("没有找到符合的环结点的数值等于%d\n",L->key);
}
else
{
q->next=L->circ;
}
p=L->head;
}
//前提为环的基础上,计算a的值。
void SearchRoot(Node *L,LinkList *p)
{
LinkList *fast,*slow,*head;
if (L->head==NULL||p==NULL) return;
head=L->head;
fast=p; slow=head;
while(fast!=slow)
{
fast=fast->next;
slow=slow->next;
L->a++;
}
//slow和fast指针两者又相遇了,则恢复快、慢指针,计算环长r
fast=fast->next->next;
slow=slow->next;
L->r++;
while(fast!=slow)
{
fast=fast->next->next;
slow=slow->next;
L->r++;
}
L->o=fast;
printf("环长r=%d:环内数据有%d ",L1->r,L->o->data);
p=L->o->next;
while(p!=L->o)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n头结点到环入口的长度a=%d \n环入口的数据o.data=%d\n",L1->a,L1->o->data);
}
bool isloop(Node *L)
{
LinkList *fast,*slow,*head;
if (L->head==NULL) return false;
head=L->head;
fast=head; slow=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if (!fast)
{
return false;
}
if (fast==slow)
{
L->meet=fast;
printf("在data=%d处第一次相遇\n",fast->data);
return true;
}
}
return false;
}
int main()
{
int i;
initNode(L1);
if (isloop(L1))
{
SearchRoot(L1,L1->meet);
}
else
{
printf("UNLoop\n");
}
system("pause");
}
运行结果:请输入L->len个数和人工设定的环入口值L->key
5 3
1 2 3 4 5
在data=4处第一次相遇
环长r=3:环内数据有3 4 5
头结点到环入口的长度a=2
环入口的数据o.data=3
请按任意键继续. . .
请输入L->len个数和人工设定的环入口值L->key
5 31
1 2 3 4 5
没有找到符合的环结点的数值等于31
UNLoop
请按任意键继续. . .
判断两个链表是否相交部分代码:因为L2输入的问题,代码不全,如L2可确定,则可运行,而initNode()、isloop()和SearchRoot()参考上面代码。
bool isin(LinkList *m, Node *L, LinkList *Lm)
{
if (m==NULL||L->head==NULL||Lm==NULL) return false;
int count=0;//用count来表示L2内环入口o走过的次数,当>=2时且还没有找到,则L1->o不在L2内
LinkList *p;
p=L->head;
while(p!=m)
{
if (p==Lm) count++;
if (count>=2) return false;
}
if (count<=1) return true;
}
//search the end point of the LinkList
LinkList * rear(Node *L)
{
if (L->head==NULL) return NULL;
LinkList *p;
p=L->head;
while(p->next!=NULL)
{
p=p->next;
}
return p;
}
int main()
{
int i,iter=0;
initNode(L1);
input(L2);//需要自己输入,不能用initNode(L2),因为初始化LinkList时,内存地址是随机的
if (isloop(L1)&&isloop(L2))//L1和L2都是环
{
SearchRoot(L1,L1->meet);
SearchRoot(L2,L2->meet);
if (isin(L1->o,L2,L2->o))//L1的入口O是否在L2内
{
iter=1;
}
}
else
{
if(!isloop(L1)&&!isloop(L2))//L1和L2都不是环
{
if (rear(L1)==rear(L2))//尾指针是否相等
{
iter=1;
}
else
{
iter=0;
}
}
}
if (iter)
{
printf("相交\n");
}
else
{
printf("不相交\n");
}
system("pause");
}
如果链表无环,有可以采用下面的方法:
1.两个链表首尾链接,判断是否存在环;如有相同的结点,则必有环;
2.判断一个链表的尾指针,是否是另一个链表的尾指针;
3.计算出两个链表的长度,将长的那个走到与短的那个长度相等,然后两个链表一起走,判断是否有相同的结点;这个方法也可变化为查找二叉树的两个结点的最近父结点。
其中自己的程序出现的问题:1.在init()时,要先L=(Node*)malloc(sizeof(Node));否则运行时总提示调试,但不说明问题出现在哪。2.init()中initNode(Node *&L)没有加&,错的第二次了,而值改变没有关系,因为是全局变量,如局部,则也加&;3.SearchRoot(Node *L,LinkList *p)中只判断L->head==NULL,而没有判断p==NULL,这也出现了运行时调试错误;4.SearchRoot()中,while(){ }中,fast=fast->next->next,内部没有再次判断,也出现调试错误。总结,大部分都是在安全判断上考虑不周。