編程之美:編程判斷兩個鏈表是否相交


編程判斷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;
} 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM