程式設計之美3.6——程式設計判斷兩個連結串列是否相交
問題:
給出兩個單向連結串列的頭指標,而兩個連結串列都可能帶環,判斷這兩個連結串列是否相交,並且給出他們相交的第一個節點。
(1)判斷連結串列是否存在環
設定兩個連結串列指標(fast, slow),初始值都指向連結串列頭結點,然後連個指標都往前走,不同的是slow每次前進一步,fast每次前進兩步,如果存在環,兩個指標必定相遇。
(2)若連結串列有環,找到環的入口點
當fast與slow相遇時,slow還沒走完連結串列,而fast已經在環內迴圈了n圈了,假設slow在相遇前走了s步,則fast走了2s步,設環長為r,有2s=s+nr,即s=nr.
由上圖可知a+x=s, x+y=r,而我們的目標是找到a的位置。設上圖那個拱起的曲線的長度為y,有a+x=s=nr=(n-1)r+r=(n-1)r+y+x,則a=(n-1)r+y. 這個公式告訴我們,從連結串列頭和相遇點分別設一個指標,每次各走一步,這兩個指標必定相遇,且相遇的第一個點為環入口點。
(3)若兩個連結串列都不存在環,找出兩個連結串列相交的第一個節點
1. 將其中一個連結串列首尾相連,判斷另一個連結串列是否存在環,如果存在,則兩個連結串列相交,且找出來的環入口點即為相交的第一個點。
2. 首先遍歷兩個連結串列,記下兩個連結串列的長度,長連結串列從起點先前進len_max-len_min步,然後兩個連結串列再一起前進,每次一步,相遇的第一個點即為相交的第一個點。
(4)若兩個連結串列都存在環,找出兩個連結串列相交的第一個節點
通過方法(1)我們能夠分別找出兩個連結串列的相遇點pos1, pos2,然後還是使用兩個指標fast和slow,都初始化為pos1,且fast每次前進2步,slow每次前進1步。若fast指標在遇到slow前,出現fast等於pos2或fast->next等於pos2,則說明兩個連結串列相交,否則不相交。若兩連結串列相交,我們可知pos2肯定是兩個連結串列的一個相交點,將這個點看做兩個連結串列的終止節點,使用(3)中的解法,即可找到兩個連結串列相交的第一個節點。
#include <iostream> #include <algorithm> using namespace std; struct Link { int data; Link *next; }; // 插入節點 void insertNode(Link *&head, int data) { Link *node = new Link; node->data = data; node->next = head; head = node; } // 判斷連結串列是否存在環 Link* hasCycle(Link* head) { Link *fast, *slow; slow = fast = head; while (fast && fast->next) { fast = fast->next->next; slow = slow->next; if (fast == slow) return slow; } return NULL; } // 確定環的入口點,pos表示fast與slow相遇的位置 Link* findCycleEntry(Link* head, Link* pos) { while (head != pos) { head = head->next; pos = pos->next; } return head; } // 找到兩個連結串列相交的第一個交點(連結串列可能會有環) Link* findFirstCross(Link* head1, Link* head2) { Link* pos1 = hasCycle(head1); Link* pos2 = hasCycle(head2); // 一個連結串列有環,另一個連結串列沒環,那肯定沒有交點 if (pos1 && !pos2 || !pos1 && pos2) return NULL; Link *nd1, *nd2; // 兩個連結串列都沒有環 if (!pos1 && !pos2) { // 記下兩個連結串列的長度 int len1, len2; len1 = len2 = 0; nd1 = head1; while (nd1) {len1++;nd1=nd1->next;} nd2 = head2; while (nd2) {len2++;nd2=nd2->next;} // 較長連結串列的連結串列的nd先走dif步 int dif; nd1 = head1; nd2 = head2; if (len1 >= len2) { dif = len1 - len2; while (dif--) nd1=nd1->next; } else { dif = len2 - len1; while (dif--) nd2=nd2->next; } // 之後兩個nd再一起走,直到某個nd為NULL(不相交) // 或直到nd相等(即為第一個交點) while (nd1 && nd2) { if (nd1==nd2) return nd1; nd1=nd1->next; nd2=nd2->next; } return NULL; } // 兩個連結串列都有環 if (pos1 && pos2) { // 判斷這兩個環是不是同一個環 Link *tmp = pos1; do { if (pos1 == pos2 ||pos1->next == pos2) break; pos1 = pos1->next->next; tmp = tmp->next; }while (pos1!=tmp); // 兩個連結串列的環不是同一個環,所以沒有交點 if (pos1 != pos2 && pos1->next != pos2) return NULL; // 兩個連結串列有共同的交點pos1,現在求第一個交點 int len1, len2; len1 = len2 = 0; Link *nd1, *nd2; nd1 = head1; while (nd1 != pos1) {len1++;nd1=nd1->next;} nd2 = head2; while (nd2 != pos1) {len2++;nd2=nd2->next;} // 較長連結串列的連結串列的nd先走dif步 int dif; nd1 = head1; nd2 = head2; if (len1 >= len2) { dif = len1 - len2; while (dif--) nd1 = nd1->next; } else { dif = len2 - len1; while (dif--) nd2 = nd2->next; } // 之後兩個nd再一起走,直到nd相等(即為第一個交點) while (nd1!=pos1 && nd2!=pos1) { if (nd1 == nd2) return nd1; nd1 = nd1->next; nd2 = nd2->next; } return pos1; } } int total[] = {8, 2, 5}; int C[10] = {15, 14, 13, 12, 11, 10, 9, 8}; int B[10] = {7, 6}; int A[10] = {5, 4, 3, 2, 1}; int main() { Link *headB, *headA; headB = headA = NULL; int i; for (i=0; i<total[0]; i++) insertNode(headB, C[i]); Link *nd = headB; while (nd->next) nd = nd->next; // 8->9->10->11->12->13->14->15->10->11->12->... nd->next = headB->next->next; headA = headB; // B: 6->7->8->9->10->...->15->10->... for (i=0; i<total[1]; i++) insertNode(headB, B[i]); // A: 1->2->3->4->5->8->9->10->...->15->10->... for (i=0; i<total[2]; i++) insertNode(headA, A[i]); // find: 8 Link *pos = findFirstCross(headA, headB); if (pos) printf("yes: %d\n", pos->data); else printf("no\n"); }
轉自:http://blog.csdn.net/linyunzju/article/details/7753548