如何判断两个链表是否相交并求出相交点

排除链表存在环的情况

此情况的意思就是普通的单链表是否相交问题。

相交是什么意思?注意不是单纯的节点的数值域相等,相交的意思是两个链表的部门节点的是同一个,就是这些节点为这两个链表共有。

如何判断两个链表是否相交并求出相交点_第1张图片
链表的定义参照: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;
}

方法四

首先保存第二条链表的第一个节点,然后第二个链表整个接到第一个链表的后面,从第二个链表的第一个节点往后遍历,如果能再走到第二条链表的第一个节点位置,说明第二条链表的后部分节点与第一条链表共用,这样才会是后移的指针重新指向第二条链表的第一个节点。
此方法会破坏原有的链表的结构,所以这里我不提供代码,感兴趣可以自行尝试。

方法五

首先保存第二条链表的第一个节点,然后把第二条链表首尾链接形成一个大环,从第一条链表开始走,如果指针能够指向到第二条链表的第一个节点,说明链表相交。如图:
如何判断两个链表是否相交并求出相交点_第2张图片

原理:第二条链表首尾相连时,从第一条链条的开头若想走到第二条的开头,必须经过第二条的链表的后部分,即相交。

此方法会破坏原有的链表的结构,所以这里我不提供代码,感兴趣可以自行尝试。

包括链表存在环的情况

判断相交的步骤:
判断链表是否有环,请看这篇博客: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;
}

方法二

利用上面的判断是否相交的方法五,把第二条链表首尾相连形成一个环
如何判断两个链表是否相交并求出相交点_第3张图片
问题变成了求第一条链表的环入口点: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();  //寻找环入口点
}

带环的情况

无非两种情况,一种是交于环外,一种交于环内
如何判断两个链表是否相交并求出相交点_第4张图片

如何判断两个链表是否相交并求出相交点_第5张图片

通过上图发现如果交于环内,出现了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
}

你可能感兴趣的:(不刷题心里难受,链表)