3.C语言数据结构 判断两个单链表相交

                                                        判断两个单链表相交
判断 2 个单链表是否相交,是一个老生常谈但又极具思考性的面试题,本节就在读者已经掌握单链表及其基本操作的基础上,就此问题给大家做深入地讲解。
首先,读者要搞清楚“相交”的含义。 所谓相交,是指有公共的部分,而 2 个单链表相交,则意味着它们 有公共的节点 公共节点的数量可以是 1 个或者多个
通过前面的学习我们知道,单链表是线性表的一种,如果我们将 2 个单链表看做 2 条线段的话,图 1 模拟了 2 条线段相交的所有可能情况。

注意,结合“单链表中每个节点有且仅有 1 个指针域”的特性,如 1 所示的这 3 种情况中,只有第 2 种情况符合单链表的特性,另外 2 种情况则破坏了此特性。经过以上的分析,本节要验证 2 个单链表是否相交,实际上等同于判断 2 个单链表是否和图 1② 所示的存储结构相同
判断 2 个单链表(下文分别称它们为链表 1 和链表 2 )是否相交,常用的方法有如下几种。
1) 分别遍历链表 1 和链表 2,对于链表 1 中的每个节点,依次和链表 2 中的各节点进行比对,查看 它们的存储地址是否相同,如果相同,则表明它们相交 ;反之,如果链表 1 中各节点的存储地址,和链表 2 中的各个节点都不相同,则表明它们不相交。
注意,此方法中 比较的是节点的存储地址 ,而非数据域中存储的元素。原因很简单,数据域中存储元素值相同,并不是 2 个单链表相交的充分条件,因为 2 个不相交的链表中很可能存有相同的元素。
仍以前面章节中构建的单链表为例:
typedef struct Link {
	int elem;			//表示数据域
	struct Link* next;	//表示指针域
}link;//
在此基础上,判断 2 个单链表是否相交的实现代码为:
//自定义的bool类型
typedef enum bool{
	False = 0;
	True = 1;
}bool;
//L1和L2表示两个单链表,函数返回值为True表示相交,返回False表示不相交

bool LinkIntersect(link* L1, link* L2)
{
	link* p1 = L1;
	link* p2 = L2;
	//遍历L1链表L1的各个节点
	while (p1)
	{
		//遍历L2链表,针对每个p1,依次和p2所指节点做比较
		while (p2) {
			//p1、p2中记录的就是各个节点的存储地址,直接比较即可
			if (p1 == p2) {
				return True;
			}
			p2 = p2->next;
		}
		p1 = p1->next;
	}
	return False;
}
通过分析 LinkIntersect() 函数的实现过程不难得知,无论 2 个链表是否相交,此实现方式的时间复杂度为 O(n2)。
2) 实际上,第 1 种实现方案还可以进一步优化。结合图 1②,2 个单链表相交有一个必然结果,即这 2 个链表的最后一个节点必定相同;反之,如果 2 个链表不相交,则这 2 个链表的最后一个节点必定不相同。

 由此,可以对以上实现代码进行优化:

//L1和L2为2个单链表,函数返回True表示链表相交,返回False表示不相交
bool LinkIntersect(link* L1, link* L2)
{
	link* p1 = L1;
	link* p2 = L2;
	//找到L1链表中的最后一个节点
	while (p1->next)
	{
		p1 = p1->next;
	}
	//找到L2链表中的最后一个节点
	while (p2->next)
	{
		p2 = p2->next;
	}
	//判断L1和L2链表最后一个节点是否相同
	if (p1 == p2) {
		return True;
	}
	return False;
}
显然经过优化,该函数的时间复杂度就缩小为 O(n)。
3) 针对第 1 种实现方案的优化,除了第 2 种方式,还有一种思想。
假设 L1 和 L 2 相交,则两个链表中相交部分节点的数量一定是相等的。如图 2 所示:
可以看到, L1 和 L2 相交,绿色部分节点为 L1 和 L2 链表的相交部分
也就是说, 如果 2 个链表相交,那么它们相交部分所包含的节点个数一定相等 。在此基础上,我们可以这样优化第 1 种实现方案,以图 2 中的 L1 和 L2 为例,从 L1 尾部选取和 L2 链表等长度的一个子链表(也也就是图 3中的 temp 子链表),同时遍历 temp 和 L2 链表,依次判断 2 个遍历点是否相同,如果相同则表明 L1 和 L2相交;反之则不相交。

 

 此实现方案的实现代码如下:

//L1和L2为2个单链表,函数返回True表示链表相交,返回False表示不相交
bool LinkIntersect(link* L1, link* L2)
{
	link* plong = L1;
	link* pshort = L2;
	link* temp = NULL;
	int num1 = 0, num2 = 0, step = 0;
	//得到L1的长度
	while (plong) {
		num1++;
		plong = plong->next;
	}
	//得到L2的长度
	while (pshort) {
		num2++;
		pshort = pshort->next;
	}
	//重置plong和pshort,使plong代表较长的链表,pshort代表较短的链表
	plong = L1;
	pshort = L2;
	step = num1 - num2;
	if (num1 < num2) {
		plong = L2;
		pshort = L1;
		step = num2 -num1;
	}
	//将temp指向与pshort相同的那个节点
	temp = plong;
	while (step) {
		temp = temp->next;
		step--;
	}
	//逐个作比较temp和short链表的节点是否相同
	while (temp && pshort) {
		//可以比较指向指针地址是否相同相同
		if (temp == pshort) {
			return True;
		}
		temp = temp->next;
		pshort = pshort->next;
	}
	return False;
}

相比第 2 种方案,此方法的实现逻辑虽然复杂,但优点是,该方法可以找到 2 个单链表相交的交点(也就是相交时的第一个节点),也就是使 LinkIntersect() 函数返回 True 时的 temp 指针指向的那个节点。另外,此方案的时间复杂度也为 O(n)。 

总结
总的来说,本节讲解了 3 种“判断 2 个链表是否相交”的方法,其中第 2、3 种方案的时间复杂度都比第 1 种要小。
从另一个角度比较这 3 种方案,第 1 种和 第 3 种在判断“2 个链表是否相交”的同时,还能找到它们相交的交点,而第 2 种实现方案则不具备这个功能。
如果读者想实现“判断 2 个链表是否相交,如果相加找到交点”这样的功能,只需对第 1、3 种方案的实现代码做略微调整即可。由于很简单,读者可自行尝试实现。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值