热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

编程之美:编程判断两个链表是否相交

编程判断2个链表是否相交(假设2个链表均不带环)解法二:利用计数的方法,如果我们能够判断2个链表中是否存在地址一致的节点,就可以知道这2个链表是否相交。一个简单的做法是对第一个链表的节点地址进行h

编程判断2个链表是否相交(假设2个链表均不带环)

解法二:

利用计数的方法,如果我们能够判断2个链表中是否存在地址一致的节点,就可以知道这2个链表是否相交。一个简单的做法是对第一个链表的节点地址进行hash排序,建立hash表,然后针对第二个链表的每个节点的地址查询hash表,如果在hash表中出现,那么说明有共同的节点,时间复杂度为O(L1+L2),但是同时要附加O(L1)的存储空间。

 

解法3:转化为另一已知问题

由于2个链表都没有环,我们可以把第二个链表接在第一个链表后面,如果得到的链表有环,则说明2个链表相交。

这样,我们就把问题转换为判断一个链表是否有环。

那么“两个无环单向链表”画出来只可能是2条不相干的链表或一个”Y”字形。我们只需从第二个链表开始遍历,看是否会回到起点就可以判断出来,最后,当然可别忘了恢复原来的状态,

 

解法4:

用指针p1、p2分别指向两个链表头,不断后移;最后到达各自表尾时,若p1==p2,那么两个链表必相交
复杂度为O(L1+L2),比解法3更好。

求相交的第一个节点
对于相交的第一个结点,则可求出两个链表的长度,然后用长的减去短的得到一个差值 K,然后让长的链表先遍历K个结点,然后两个链表再开始比较。还可以这样:其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在,则两个链表相交,而检测出来的依赖环入口即为相交的第一个。
#include
using namespace std;

typedef
struct Node
{
int data;
Node
* next;
Node(
int data)
{
this->data=data;
}
Node(){}
}
*LinkList;
void initList(LinkList &list)
{
list
=new Node();
list
->next=NULL;
}
void insertList(LinkList &list)
{

int val;
Node
*tail=list;
while(tail->next!=NULL)
tail
=tail->next;

while(cin>>val && val!=-1)
{
Node
*p=new Node(val);
p
->next=NULL;

tail
->next=p;
tail
=tail->next;

}
}
void listTraverse(LinkList &list)
{
Node
*p=list->next;
while(p)
{
cout
<data<<ends;
p
=p->next;
}
}
int main()
{
LinkList L;
initList(L);
insertList(L);
listTraverse(L);
cout
<endl;
cout.clear();
LinkList L2;
initList(L2);
insertList(L2);
listTraverse(L2);cout<endl;

//将第一个链表中从第四个结点起链接到第二个链表,构造两个相交的链表
Node *p=L;
for(int i=0;i<=4;i++)
{
p
=p->next;
}
Node
*q=L2;
while(q->next)
{
q
=q->next;
}

q
->next=p;//将第二个链表的尾节点 连接到L1的第5个节点中

listTraverse(L); cout
<endl;
listTraverse(L2);cout<endl;

/*用p2,p2分别指向2个表头,不断后移,最后达到表尾时,p1=p2,说明有环 */
Node
*p1,*p2;
p1
=L;
p2
=L2;
bool isCircle=false;
int count=0;
while(p1->next!=NULL) p1=p1->next;
while(p2->next!=NULL) p2=p2->next;

if(p1==p2)
{
isCircle
=true;
}
if(isCircle)
cout
<<"有环:"<<endl;
else
cout
<<"没环:"<<endl;

/*
求环节点\
*/
p1
=L;
p2
=L2;
int len1=0,len2=0,len;


while(p1->next!=NULL) { p1=p1->next;len1++;}
while(p2->next!=NULL) {p2=p2->next;len2++;}
Node
*p11=L->next,*p22=L2->next;
if(p1==p2)
{
cout
<<"有环"<<endl;
if(len1>=len2) //2个链表长度的差值
{
len
=len1-len2;
while(len--)// //遍历差值个步长 (执行abs(length1-length2)次)
p11=p11->next;
}
else
{
len
=len2-len1;
while(len--)
p22
=p22->next;
}
while(1)
{
if(p11==p22)////两个链表中地址相同的结点 (最多执行的次数为:min(length1,length2))
{
cout
<<"第一个相交的节点:"<data;
break;
}
else if(p11->next && p22->next)
{
p11
=p11->next;
p22
=p22->next;
}
}
}
//end if
else
cout
<<"2链表不相交"<<endl;




}

输入和输出:

1 2 3 4 5 6 -1
1 2 3 4 5 6

7 8 9 -1

7 8 9 

有环:
有环
第一个相交的节点:5请按任意键继续. . .

(我们故意让L2最后一个节点连接到L1的第5个节点。结果正确

 

链表中含有环的问题

1.链表中是否有环的判断
可以设置两个指针(fast,slow),初始值均指向头,slow每次向前一步,fast每次向前两步;
如果链表中有环,则fast先进入环中,而slow后进入环中,两个指针在环中必定相遇;
果fast遍历到尾部为NULL,则无环
2.链表有环,判断环的入口点
   当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(1<=n)。假设slow走了s步,则fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:

2s = s + nr
s= nr

设整个链表长L,入口环与相遇点距离为x,起点到环入口点的距离为a。
a + x = nr
a + x = (n – 1)r +r = (n-1)r + L - a
a = (n-1)r + (L – a – x)

(L – a – x)为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点
 
(L-a-x为相遇点到环入口点的距离,怎么理解,比如上面的,我们假设slow和fast在点3相遇,
启动为1,环入口点 为2,相遇点为3,走了(L-a-x)长的距离后就回到了2点。
我们在起点和环相遇点各设置一个指针,每次各走一步,必定相遇。相遇一定在2点,为什么,
因而,可以在链表头,相遇点分别设定一个指针,每次各走一步,两个指针必定相遇,则相遇第一点为环入口点
 
void insertList(LinkList &list)
{

int val;
Node
*tail=list;
while(tail->next!=NULL)
tail
=tail->next;

while(cin>>val && val!=-1)
{
Node
*p=new Node(val);
p
->next=NULL;

tail
->next=p;
tail
=tail->next;

}
//人为构造环
tail->next=list->next->next->next->next;//第二个节点
}

LinkList listLoop(LinkList
&list)
{
int isLoop=0;
LinkList fast,slow;
fast
=slow=list;

while(fast && fast->next)
{
slow
=slow->next;
fast
=fast->next->next;//fast每次两步,slow每次一步
if(fast==slow)////当两指针相等时,判断链表中有环
{
isLoop
=1;
break;
}
}
if(isLoop==1)//有环时
{
slow
=list;
while(slow!=fast)////一个头指针,一个相遇点指针,两个第一次相等时为环入口点
{
slow
=slow->next;
fast
=fast->next;
}
return slow;
}
else
{

return NULL;
}
}

int main()
{
LinkList L;
initList(L);
insertList(L);
//listTraverse(L);
cout<endl;
cout.clear();

Node *res=listLoop(L);
if(res!=NULL)
cout
<<"环入口点为:"<data;

else
cout
<<"链表中没有环"<<endl;

}

运行结果:

1 2 3 4 5 6 -1

环入口点为:2请按任意键继续. . .

 (求环的解法:

一种比较耗空间的做法是,从头开始遍历链表,把每次访问到的结点(或其地址)存入一个集合(hashset)或字典(dictionary),如果发现某个结点已经被访问过了,就表示这个链表存在环,并且这个结点就是环的入口点。这需要O(N)空间和O(N)时间,其中N是链表中结点的数目。

如果要求只是用O(1)空间、O(N)时间,应该怎么处理呢?

其实很简单,想象一下在跑道上跑步:两个速度不同的人在操场跑道上一圈一圈地跑,他们总会有相遇的时候。因此我们只需要准备两个指针,同时从链表头出发,一个每次往前走一步,另一个每次往前走两步。如果链表没有环,那么经过一段时间,第二个(速度较快的)指针就会到达终点;但如果链表中有环,两个指针就会在环里转圈,并会在某个时刻相遇。

大家也许会问,这两个指针要在环里转多少圈才能相遇呢?会不会转几千几万圈都无法相遇?实际上,第一个(速度慢的)指针在环里转满一圈之前,两个指针必然相遇。不妨设环长为L,第一个指针P1第一次进入环时,第二个指针P2在P1前方第a个结点处(0 L-a。下面这张图可以清晰地表明这种关系,经过x = L-a次移动,P1向前移动了L-a个位置(相当于后退了a),到达P1′处,而P2向前移动了2L-2a个位置(相当于后退了2a),到达P2′处,显然P1′和P2′是同一点。

判断2个链表是否相交(没说带不带环

1.先判断带不带环
2.如果都不带环,就判断尾节点是否相等
3.如果都带环,判断一链表上俩指针相遇的那个节点,在不在另一条链表上。
如果在,则相交,如果不在,则不相交。

编写判断带环的代码:

struct Node  
{
int value;
Node
* next;
};

//1.先判断带不带环
//判断是否有环,返回bool,如果有环,返回环里的节点
//思路:用两个指针,一个指针步长为1,一个指针步长为2,判断链表是否有环
bool isCircle(Node * head, Node *& circleNode, Node *& lastNode)
{
Node
* fast = head->next;
Node
* slow = head;
while(fast != slow && fast && slow)
{
if(fast->next != NULL)
fast
= fast->next;

if(fast->next == NULL)
lastNode
= fast;
if(slow->next == NULL)
lastNode
= slow;

fast
= fast->next;
slow
= slow->next;

}
if(fast == slow && fast && slow)
{
circleNode
= fast;
return true;
}
else
return false;
}

综合判断2个链表是否相交的办法:

//判断带环不带环时链表是否相交  
//2.如果都不带环,就判断尾节点是否相等
//3.如果都带环,判断一链表上俩指针相遇的那个节点,在不在另一条链表上。
bool detect(Node * head1, Node * head2)
{
Node
* circleNode1;
Node
* circleNode2;
Node
* lastNode1;
Node
* lastNode2;

bool isCircle1 = isCircle(head1,circleNode1, lastNode1);
bool isCircle2 = isCircle(head2,circleNode2, lastNode2);

//一个有环,一个无环
if(isCircle1 != isCircle2)
return false;
//两个都无环,判断最后一个节点是否相等
else if(!isCircle1 && !isCircle2)
{
return lastNode1 == lastNode2;
}
//两个都有环,判断环里的节点是否能到达另一个链表环里的节点
else
{
Node
* temp = circleNode1->next; //updated,多谢苍狼 and hyy。
while(temp != circleNode1)
{
if(temp == circleNode2)
return true;
temp
= temp->next;
}
return false;
}

return false;
}

 

 

相关问题:求链表倒数第k个结点

设置两个指针p1,p2,首先p1和p2都指向head,然后p2向前走k步,这样p1和p2之间就间隔k个节点,最后p1和p2同时向前移动,直至p2走到链表末尾。

 

更多参考:

http://blog.csdn.net/v_july_v/article/details/6447013

http://www.360doc.com/content/12/0313/14/1429048_194005252.shtml

struct ListNode
{
char data;
ListNode
* next;
};
ListNode
* head,*p,*q;
ListNode
*pone,*ptwo;

//@heyaming, 第一节,求链表倒数第k个结点应该考虑k大于链表长度的case。
ListNode* fun(ListNode *head,int k)
{
assert(k
>= 0);
pone
= ptwo = head;
for( ; k > 0 && ptwo != NULL; k--)
ptwo
=ptwo->next;
if (k > 0) return NULL;

while(ptwo!=NULL)
{
pone
=pone->next;
ptwo
=ptwo->next;
}
return pone;
}

 


推荐阅读
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 006_Redis的List数据类型
    1.List类型是一个链表结构的集合,主要功能有push,pop,获取元素等。List类型是一个双端链表的结构,我们可以通过相关操作进行集合的头部或者尾部添加删除元素,List的设 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
author-avatar
飞跃星空2502906253
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有