此情况的意思就是普通的单链表是否相交问题。
相交是什么意思?注意不是单纯的节点的数值域相等,相交的意思是两个链表的部门节点的是同一个,就是这些节点为这两个链表共有。
链表的定义参照:http://blog.csdn.net/dawn_after_dark/article/details/73610674
我们根据上图可以发现,链表相交之后,后面的部分节点全部共用,所以我们可以用2个指针分别从这两个链表头部走到尾部,最后判断尾部指针的地址信息是否一样,若一样则代表链表相交!
代码:
Node* LinkList::findByIndex(int index){ //根据索引返回节点信息
Node* p = head;
int i = 0;
if (index<0||index >getLength()) {
cout << "索引非法!" << endl;
return NULL;
}
while (p) {
if (i == index)
return p;
else {
p = p->next;
i++;
}
}
return NULL;
}
bool LinkList::isIntersect(LinkList preLinkList, LinkList forLinkList) {
Node* tail1 = preLinkList.findByIndex(preLinkList.head->value);//链表1尾部
Node* tail2 = forLinkList.findByIndex(forLinkList.head->value);//链表2尾部
if (tail1 == tail2) {
return true;
}
return false;
}
我们可以把其中一个链表的所有节点地址信息存到数组中,然后把另一个链表的每一个节点地址信息遍历数组,若相等,则跳出循环,说明链表相交。进一步优化则是进行hash排序,建立hash表。
因为我们从上图得知,相交之后部分的长度是相等的,所以我们让长链表的长度减去短链表的长度,得到相差的长度,之后让长链表从头结点开始走过这个长度,与短链表同时向后走,若指针相等,则链表相交;若走到NULL,则链表不相交。
代码:
bool LinkList::isIntersect(LinkList preLinkList, LinkList forLinkList) {
bool flag = false;
if (preLinkList.head->value > forLinkList.head->value)
flag = true;
int length = abs(preLinkList.head->value - forLinkList.head->value);//相差的长度
Node* p= preLinkList.head->next, *q= forLinkList.head->next;//此处初始化应对length=0的情况
if (length) {
if (flag) {//第一个链表长
p = preLinkList.findByIndex(length)->next;
q = forLinkList.head->next;
}
else {
p = forLinkList.findByIndex(length)->next;
q = preLinkList.head->next;
}
}
while (p != q) { //若指针相等跳出 ,可能会同时为空
p = p->next;
q = q->next;
}
if (p) //排序跳出时为空
return true;
else
return false;
}
首先保存第二条链表的第一个节点,然后第二个链表整个接到第一个链表的后面,从第二个链表的第一个节点往后遍历,如果能再走到第二条链表的第一个节点位置,说明第二条链表的后部分节点与第一条链表共用,这样才会是后移的指针重新指向第二条链表的第一个节点。
此方法会破坏原有的链表的结构,所以这里我不提供代码,感兴趣可以自行尝试。
首先保存第二条链表的第一个节点,然后把第二条链表首尾链接形成一个大环,从第一条链表开始走,如果指针能够指向到第二条链表的第一个节点,说明链表相交。如图:
原理:第二条链表首尾相连时,从第一条链条的开头若想走到第二条的开头,必须经过第二条的链表的后部分,即相交。
此方法会破坏原有的链表的结构,所以这里我不提供代码,感兴趣可以自行尝试。
判断相交的步骤:
判断链表是否有环,请看这篇博客:http://blog.csdn.net/dawn_after_dark/article/details/73742239
1.判断两个链表是否都不含有环,若都是不带环的单链表,则转向上面五种方法解决问题
2.若一个链表不带环,一个带环,则这两个链表一定不相交,因为相交之后,链表之后部分是共用,若有环,则都带环,且环的长度相等,相交点即为环的入口。
3.若都带环,根据判断是否有环的那篇博客求出其中一个链表的相遇点,然后遍历另一个链表,若相遇点的地址信息存在另一个链表上,则表明链表相交。原理如果相交的话,两链表的环上必定都是共用的节点,因为环是由尾部指向另一个节点造成的,而相交的部分必定包含尾部,所以环上必定是共有的节点,相遇点正好在环上,所以遍历另一链表,若找到相同的地址信息,必相交!
关于相遇点:http://blog.csdn.net/dawn_after_dark/article/details/73742239
代码:
Node* LinkList::findMeetingPointInCirlce() { //找到带环的链表的相遇点
Node* slow = head;
Node* fast = head;
Node* meetingPoint;
while (fast&&fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
break;
}
}
if (!(fast == NULL || fast->next == NULL)) //如果没有环,返回0
meetingPoint = slow;
else
meetingPoint = NULL;
return meetingPoint;
}
/*判断是否相交*/
bool LinkList::isIntersect(LinkList preLinkList, LinkList forLinkList) {
if (preLinkList.isContainCirle() && forLinkList.isContainCirle()) { //判断是否都有环
Node* meetingPoint = preLinkList.findMeetingPointInCirlce(); //找到其中一个的相遇点
int i = 1;
for (Node* p = forLinkList.head->next;i<=forLinkList.head->value;p = p->next) {//遍历另一个链表
if (p == meetingPoint)
break;
i++;
}
if (i <= forLinkList.head->value) //如果循环结束,i小于链表的长度,说明另一条链表有相遇点,则相交
return true;
else
return false;
}
else if (!(preLinkList.isContainCirle() || forLinkList.isContainCirle())) {//链表都不包含环,转上面的方法一
Node* tail1 = preLinkList.findByIndex(preLinkList.head->value);//链表1尾部
Node* tail2 = forLinkList.findByIndex(forLinkList.head->value);//链表2尾部
if (tail1 == tail2) {
return true;
}
return false;
}
else //一个有环,一个没环,肯定不相交
return false;
}
利用上面的判断是否相交的方法三,即可求出相交点,代码与上面类似
Node* LinkList::findIntersectPoint(LinkList preLinkList, LinkList forLinkList){ //不带环的链表求交点
bool flag = false;
if (preLinkList.head->value > forLinkList.head->value)
flag = true;
int length = abs(preLinkList.head->value - forLinkList.head->value);//相差的长度
Node* p= preLinkList.head->next, *q= forLinkList.head->next;//此处初始化应对length=0的情况
if (length) {
if (flag) {//第一个链表长
p = preLinkList.findByIndex(length)->next;
q = forLinkList.head->next;
}
else {
p = forLinkList.findByIndex(length)->next;
q = preLinkList.head->next;
}
}
while (p != q) { //若指针相等跳出 ,可能会同时为空
p = p->next;
q = q->next;
}
if (p) //排序跳出时为空
return p;
else
return NULL;
}
利用上面的判断是否相交的方法五,把第二条链表首尾相连形成一个环
问题变成了求第一条链表的环入口点:http://blog.csdn.net/dawn_after_dark/article/details/73742239
Node* LinkList::findIntersectPoint(LinkList preLinkList, LinkList forLinkList){ //不带环的链表求交点
Node* tail2 = forLinkList.findByIndex(forLinkList.head->value);//找到第二条指针的尾部
tail2->next = forLinkList.head->next; //首尾相连
return preLinkList.findEnterCircle(); //寻找环入口点
}
通过上图发现如果交于环内,出现了2个相交点,这只是视觉上的2个,其实如果把环看成链表1的,则就可以说链表2相交于链表1的B处;反过来,如果把环看成链表2的,则就可以说链表1相交于链表2的A处。所以我们求相交点时这里就默认输出B,你也可以根据需要自行都输出来。相交于环内和环外求相交点方法是不一样的,那么问题来了如何判断相交于环内还是环外?
思路:
1.首先获取到2个链表的长度,获得长度差L(因为环是共用的,所以这个差其实就是环外的差),获得2个链表的各个环入口点
2.先让一个长的链表从头开始走过L个节点。
3.再让短的链表从头与之同步后移,再保证2者不走到环入口的同时判断节点地址信息是否相同
4.若相同并未到环入口点,则相交于环外,返回相交点。
5.否则直接返回任意一个链表的环入口作为相交点即可,不理解的看图即可。
代码:
Node* LinkList::findIntersectPoint(LinkList preLinkList, LinkList forLinkList){ //带环的链表求交点
int length1 = preLinkList.getCircleLinklistLength();
int length2 = forLinkList.getCircleLinklistLength(); //获得各自的链表总长度
Node* preEntry = preLinkList.findEnterCircle();
Node* forEntry = forLinkList.findEnterCircle(); //找到各自的环入口点
int length;
Node* pre = preLinkList.head->next;
Node* forth = forLinkList.head->next; //初始化各自链表的第一个节点的指针
if (length1 > length2) { //根据谁长谁短 决定谁后移指针
length = length1 - length2;
for (int i = 1;i <= length;pre = pre->next, i++);
}
else {
length = length2 - length1;
for (int i = 1;i <= length;forth = forth->next, i++);
}
while (pre != preEntry&&forth != forEntry&&pre != forth) { //向后移,注意是否走到环入口点
pre = pre->next;
forth = forth->next;
}
/*结束循环有3种情况
1.2个链表任意一个或全部同时走到了环入口点,结束了循环
2.2个链表任意一个或全部同时走到了环入口点,并且pre=forth,结束了循环
3.没有到达任何一个环入口点,pre=forth结束了循环
*/
if (pre == forth) //对应情况2、3,如果是情况2,说明2个链表的环入口点相同;如果情况3,则说明在任何一个环入口之前找到了相交点
return pre;
else
return preEntry;//对应情况1
}