7.微软亚院之编程判断俩个链表是否相交

微软亚院之编程判断俩个链表是否相交

给出俩个单向链表的头指针,比如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,内部没有再次判断,也出现调试错误。总结,大部分都是在安全判断上考虑不周。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值