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


    编程判断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<iostream>
    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<<p->data<<ends;
            p=p->next;
        }
    }
    int main()
    {
        LinkList L;
        initList(L);
        insertList(L);
        listTraverse(L);
        cout<<endl<<endl;
        cout.clear();
        LinkList L2;
        initList(L2);
        insertList(L2);
        listTraverse(L2);cout<<endl<<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<<endl;
        listTraverse(L2);cout<<endl<<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<<"第一个相交的节点:"<<p11->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<<endl;
        cout.clear();
        
        Node *res=listLoop(L);
        if(res!=NULL)
            cout<<"环入口点为:"<<res->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 < a < L),设经过x次移动后两个指针相遇,那么应该有0+x =(a + 2x) (mod L),显然x = 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;
    } 
  • 相关阅读:
    牛客多校(2020第三场)C Operation Love
    牛客多校(2020第三场)C Operation Love
    牛客多校(2020第三场)B Classical String Problem
    牛客多校(2020第三场)B Classical String Problem
    牛客多校(2020第三场)L Problem L is the Only Lovely Problem
    牛客多校(2020第三场)L Problem L is the Only Lovely Problem
    一个图形或者控件旋转时 判断方向逆时针还是顺时针
    为什么 dll 改名字之后无法使用
    C# 几个特殊运算符的理解和Nullable<T> 的研究
    再次深入 C# Attribute
  • 原文地址:https://www.cnblogs.com/youxin/p/3303172.html
Copyright © 2020-2023  润新知