一、問題描述
有兩個鏈表,判斷是否相交並求出相交的點?
二、問題分析
大家看到題目會不由自主的想起一個很普遍的情況,就是下面

但是這個題目有一個陷阱就是,沒有講明兩個鏈表的結構,沒有很好地給出,其實有三種情況
(1)當兩個鏈表都無環,如上面
(2)當一個鏈表有環,另一個鏈表無環
(3)當兩個鏈表都有環
這三種情況,下面一一講解這些情況下兩個鏈表是否相交以及相交點。
三、問題解析
(一)當兩個鏈表都無環

3.1 方法1
鏈表相交后,發現后面的結點全部公用,我們可以這樣:從兩個鏈表的頭走到尾,判斷鏈尾地址信息是不是一樣,如果是則相交,反之則不交即可。
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; }
3.2 方法二
我們可以根據上圖,相交之后部分的長度是一樣的,相等的,所以我們可以這樣讓長鏈表的長度減去稍微較短的長度,得到兩個鏈表的相差長度,再讓長鏈表從頭結點開始遍歷這個長度,與此同時,短鏈表也向后走,若指針相等,鏈表就相交,反之,則不交
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; }
求相交點
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;
}
(二 )當一個鏈表不帶環,另一個帶環
如果一個鏈表不帶環,另一個鏈表帶環,兩個鏈表一定沒有相交點。因為相交之后,鏈表肯定有部分是共用,若有環,都會帶環,而且環的長度一樣,相交點也是環的入口處。
(3)兩個都帶環
就有兩種情況,一種交於環外,另外是交與環內,如下圖

圖1

圖2
通過上圖2發現,如果把環看成鏈表1的,則鏈表2相交於B點,反過來把環看出鏈表2的,則鏈表1相交於A處,所以你看的B處和A處,只是視覺上的,如果環是兩個鏈表的,則相交於環的任何結點上。
思路:
(1)因為環是大家共用的,我們先求出兩個鏈表的差L,也就是環外的差,並獲得鏈表的環入口點
(2)讓一個較長的鏈表從開始走過L個結點
(3)然后再讓短的鏈表從頭開始與之同步移動,保證兩個鏈表不走到環的入口時,判斷結點地址是否相同
(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
}
以上就是鏈表的相交和相交點的思考,准備去BAT面試的大佬,還是可以讀一下的。
