編程判斷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個節點。結果正確。
鏈表中含有環的問題:
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)

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; }