【编程题目】编程判断俩个链表是否相交 ☆

第 7 题(链表)
微软亚院之编程判断俩个链表是否相交
给出俩个单向链表的头指针,比如 h1,h2,判断这俩个链表是否相交。
为了简化问题,我们假设俩个链表均不带环。
问题扩展:
1.如果链表可能有环列?
2.如果需要求出俩个链表相交的第一个节点列?

看到这个题目我很困惑。如果链表的结构是下面这个样子

typedef struct ListNode
{
    int m_Value;
    ListNode * p_Next;
}ListNode;

那么一旦有相交,链表的后端就都是一模一样的了啊?因为交叉点只可能有一个p_Next,那只要判断两个链表最后一个不为空的结点是否相同就可以了。

对有环列的,那环只能出现在链表最后面了。扩展的第1、2问只要对两个链表做两层循环判断就可以了。

但总觉得我理解的不对。对于带环链表的创建我也有点困惑。

在网上看了看,我的理解居然是对的。 我判断环的方法是用一个指针数组缓存已经出现过的点,比较新的点在之前是否出现过。 对于带环的相交判断是分开环的部分和无环的部分,先判断没有环的部分是否相交,如果不相交,再判断一个环结点是否在另一个环中出现。

缺点:需要数组来缓存,链表最大长度有限制。

/*
第 7 题(链表)
微软亚院之编程判断俩个链表是否相交
给出俩个单向链表的头指针,比如 h1,h2,判断这俩个链表是否相交。
为了简化问题,我们假设俩个链表均不带环。
问题扩展:
1.如果链表可能有环列?
2.如果需要求出俩个链表相交的第一个节点列?
start time = 11:10
end time = 
*/

#include <stdio.h>
#include <stdlib.h>

typedef struct ListNode
{
    int m_Value;
    ListNode * p_Next;
}ListNode;

//无环
bool isCross(ListNode * p1, ListNode * p2)
{
    ListNode * x = p1;
    ListNode * y = p2;
    while(x->p_Next != NULL)
    {
        x = x->p_Next;
    }
    while(y->p_Next != NULL)
    {
        y = y->p_Next;
    }
    return (x == y);
}

//判断是否有环 若有返回环的交叉结点 没有返回NULL
ListNode * isCircle(ListNode * pHead)
{
    if(pHead == NULL)
        return NULL;

    ListNode * p[100];
    int n = 0;
    p[n] = pHead;
    n++;
    while(p[n - 1]->p_Next != NULL)
    {
        p[n] = p[n - 1]->p_Next;
        n++;
        for(int i = 0; i < n - 1; i++)
        {
            if(p[i] == p[n - 1])
            {
                return p[i];
            }
        }
        if(n >= 100)
        {
            printf("input is too large");
            return NULL;
        }
    }
    return NULL;
}

ListNode * isCrossWithCircle(ListNode * p1, ListNode *p2)
{
    ListNode * c1 = isCircle(p1);
    ListNode * c2 = isCircle(p2);
    if(c1 == NULL && c2 == NULL) //都没有环
    {
        c1 = p1;
        c2 = p2;
        while(c1 != NULL)
        {
            while(c2 != NULL)
            {
                if(c1 == c2)
                    return c1;
                c2 = c2->p_Next;
            }
            c1->p_Next;
        }
    }
    else if(c1 != NULL && c2 != NULL) //都有环
    {
        ListNode * h1 = p1;
        ListNode * h2 = p2;
        while(h1 != c1) //判断非环部分是否相交
        {
            while(h2 != c2)
            {
                if(h1 == h2)
                {
                    return h1;
                }
                h2 = h2->p_Next;
            }
            h1 = h1->p_Next;
        }
        h1 = c1;
        h2 = c2;
        do //判断非环部分是否相交 第一个公共点是 两个交叉点中的任意一个
        {
            if(h1 == h2)
            {
                return h1;
            }
            h1 = h1->p_Next;
        }while(h1 != c1);    
    }

    return NULL;
}
int main()
{
    ListNode n1 , n2, n3, n4, n5, n6, n7, n8, n9;
    n1.m_Value = 1; n1.p_Next = &n2;
    n2.m_Value = 2; n2.p_Next = &n3;
    n3.m_Value = 3; n3.p_Next = &n7;
    n4.m_Value = 4; n4.p_Next = &n5;
    n5.m_Value = 5; n5.p_Next = &n6;
    n6.m_Value = 6; n6.p_Next = &n8;
    n7.m_Value = 7; n7.p_Next = &n8;
    n8.m_Value = 8; n8.p_Next = &n9;
    n9.m_Value = 9; n9.p_Next = &n7;

    //bool flag = isCross(&n1, &n4);
    ListNode * a = isCrossWithCircle(&n1, &n4);
    return 0;
}

网上发现,判断是否有环和环结点有专门的方法:

http://www.cppblog.com/humanchao/archive/2008/04/17/47357.aspx

一、判断链表是否存在环,办法为:

设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表)程序如下:

bool IsExitsLoop(slist *head)
{
    slist *slow = head, *fast = head;

    while ( fast && fast->next ) 
    {
        slow = slow->next;
        fast = fast->next->next;
        if ( slow == fast ) break;
    }

    return !(fast == NULL || fast->next == NULL);
}

二、找到环的入口点

当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)循环内环+相遇点到环入口点,于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。程序描述如下:

slist* FindLoopPort(slist *head)
{
    slist *slow = head, *fast = head;

    while ( fast && fast->next ) 
    {
        slow = slow->next;
        fast = fast->next->next;
        if ( slow == fast ) break;
    }

    if (fast == NULL || fast->next == NULL)
        return NULL;

    slow = head;
    while (slow != fast)
    {
         slow = slow->next;
         fast = fast->next;
    }

    return slow;
}

判断两个单链表是否相交,如果相交,给出相交的第一个点(两个链表都不存在环)。

比较好的方法有两个:

一、将其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在,则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。

二、如果两个链表相交,那个两个链表从相交点到链表结束都是相同的节点,我们可以先遍历一个链表,直到尾部,再遍历另外一个链表,如果也可以走到同样的结尾点,则两个链表相交。

这时我们记下两个链表length,再遍历一次,长链表节点先出发前进(lengthMax-lengthMin)步,之后两个链表同时前进,每次一步,相遇的第一点即为两个链表相交的第一个点。

看过答案后修改代码:

/*
第 7 题(链表)
微软亚院之编程判断俩个链表是否相交
给出俩个单向链表的头指针,比如 h1,h2,判断这俩个链表是否相交。
为了简化问题,我们假设俩个链表均不带环。
问题扩展:
1.如果链表可能有环列?
2.如果需要求出俩个链表相交的第一个节点列?
start time = 11:10
end time = 
*/

#include <stdio.h>
#include <stdlib.h>

typedef struct ListNode
{
    int m_Value;
    ListNode * p_Next;
}ListNode;

ListNode * isCircle(ListNode * pHead) //判断是否有环,返回环的起始点指针
{
    ListNode * slow = pHead;
    ListNode * fast = pHead;

    while(fast != NULL && fast->p_Next != NULL)
    {
        slow = slow->p_Next;
        fast = fast->p_Next->p_Next;
        if(slow == fast)
            break;
    }
    if(fast == NULL || fast->p_Next == NULL)
        return NULL;

    slow = pHead;
    while(slow != fast)
    {
        slow = slow->p_Next;
        fast = fast->p_Next;
    }
    return slow;
}

ListNode * isCross(ListNode * p1, ListNode * p2)
{
    ListNode * c1 = isCircle(p1);
    ListNode * c2 = isCircle(p2);
    if(c1 == c2) //都不是带环的 或者有环且环相同 只可能在环结点前面相交
    {
        ListNode * h1 = p1;
        ListNode * h2 = p2;
        int len1 = 0;
        int len2 = 0;
        while(h1->p_Next != c1) //统计从起点到相同点有多少步,以及c1 c2之前的那个结点
        {
            len1++;
            h1 = h1->p_Next;
        }
        while(h2->p_Next != c2)
        {
            len2++;
            h2 = h2->p_Next;
        }
        if(h1 == h2) //无环且相交 或 有环但相交点在非环部分
        {
            h1 = p1;
            h2 = p2;
            if(len1 > len2)  //长度长的链先走多出来的部分,剩下的同步走,直到相同
            {
                for(int i = 0; i < len1 - len2; i++)
                {
                    h1 = h1->p_Next;
                }
            }
            else
            {
                for(int i = 0; i < len2 - len1; i++)
                {
                    h2 = h2->p_Next;
                }
            }

            while(h1 != h2)
            {
                h1 = h1->p_Next;
                h2 = h2->p_Next;
            }
            return h1;

        }
        else if(c1 != NULL) //恰在环交点相交
        {
            return c1;
        }
    }
    else if(c1 != NULL && c2 != NULL) //如果两个环不同
    {
        ListNode * h1 = c1; //判断在环1内能否找到环2的点
        do
        {
            if(h1 == c2)
                return h1;
            h1 = h1->p_Next;
        }while(h1 != c1);
    }

    return NULL; //其他情况均不会相交
}

int main()
{
    ListNode n1 , n2, n3, n4, n5, n6, n7, n8, n9;
    n1.m_Value = 1; n1.p_Next = &n2;
    n2.m_Value = 2; n2.p_Next = &n3;
    n3.m_Value = 3; n3.p_Next = &n7;
    n4.m_Value = 4; n4.p_Next = &n5;
    n5.m_Value = 5; n5.p_Next = &n6;
    n6.m_Value = 6; n6.p_Next = &n8;
    n7.m_Value = 7; n7.p_Next = &n8;
    n8.m_Value = 8; n8.p_Next = &n9;
    n9.m_Value = 9; n9.p_Next = &n7;

    ListNode * a = isCross(&n1, &n4);
    return 0;
}
原文地址:https://www.cnblogs.com/dplearning/p/3902595.html