编程之美3.6——编程判断两个链表是否相交

问题:

给出两个单向链表的头指针,而两个链表都可能带环,判断这两个链表是否相交,并且给出他们相交的第一个节点。


解法:参考http://blog.csdn.net/ldong2007/article/details/4544203

(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)中的解法,即可找到两个链表相交的第一个节点。

[cpp]  view plain  copy
  1. #include <iostream>  
  2. #include <algorithm>  
  3. using namespace std;  
  4.   
  5. struct Link  
  6. {  
  7.     int data;  
  8.     Link *next;  
  9. };  
  10.   
  11. // 插入节点  
  12. void insertNode(Link *&head, int data)  
  13. {  
  14.     Link *node = new Link;  
  15.     node->data = data;  
  16.     node->next = head;  
  17.     head = node;  
  18. }  
  19.   
  20. // 判断链表是否存在环  
  21. Link* hasCycle(Link* head)  
  22. {  
  23.     Link *fast, *slow;  
  24.     slow = fast = head;  
  25.     while (fast && fast->next)  
  26.     {  
  27.         fast = fast->next->next;  
  28.         slow = slow->next;  
  29.         if (fast == slow)  
  30.             return slow;  
  31.     }  
  32.     return NULL;  
  33. }  
  34.   
  35. // 确定环的入口点,pos表示fast与slow相遇的位置  
  36. Link* findCycleEntry(Link* head, Link* pos)  
  37. {  
  38.     while (head != pos)  
  39.     {  
  40.         head = head->next;  
  41.         pos = pos->next;  
  42.     }  
  43.     return head;  
  44. }  
  45.   
  46. // 找到两个链表相交的第一个交点(链表可能会有环)  
  47. Link* findFirstCross(Link* head1, Link* head2)  
  48. {  
  49.     Link* pos1 = hasCycle(head1);  
  50.     Link* pos2 = hasCycle(head2);  
  51.     // 一个链表有环,另一个链表没环,那肯定没有交点  
  52.     if (pos1 && !pos2 || !pos1 && pos2)  
  53.         return NULL;  
  54.     Link *nd1, *nd2;  
  55.     // 两个链表都没有环  
  56.     if (!pos1 && !pos2)  
  57.     {  
  58.         // 记下两个链表的长度  
  59.         int len1, len2;  
  60.         len1 = len2 = 0;  
  61.         nd1 = head1;  
  62.         while (nd1) {len1++;nd1=nd1->next;}  
  63.         nd2 = head2;  
  64.         while (nd2) {len2++;nd2=nd2->next;}  
  65.         // 较长链表的链表的nd先走dif步  
  66.         int dif;  
  67.         nd1 = head1; nd2 = head2;  
  68.         if (len1 >= len2)  
  69.         {  
  70.             dif = len1 - len2;  
  71.             while (dif--) nd1=nd1->next;  
  72.         }  
  73.         else  
  74.         {  
  75.             dif = len2 - len1;  
  76.             while (dif--) nd2=nd2->next;  
  77.         }  
  78.         // 之后两个nd再一起走,直到某个nd为NULL(不相交)  
  79.         // 或直到nd相等(即为第一个交点)  
  80.         while (nd1 && nd2)  
  81.         {  
  82.             if (nd1==nd2)  
  83.                 return nd1;  
  84.             nd1=nd1->next;  
  85.             nd2=nd2->next;  
  86.         }  
  87.         return NULL;  
  88.     }  
  89.     // 两个链表都有环  
  90.     if (pos1 && pos2)  
  91.     {  
  92.         // 判断这两个环是不是同一个环  
  93.         Link *tmp = pos1;  
  94.         do  
  95.         {  
  96.             if (pos1 == pos2 ||pos1->next == pos2)  
  97.                 break;  
  98.             pos1 = pos1->next->next;  
  99.             tmp = tmp->next;  
  100.         }while (pos1!=tmp);  
  101.         // 两个链表的环不是同一个环,所以没有交点  
  102.         if (pos1 != pos2 && pos1->next != pos2)  
  103.             return NULL;  
  104.         // 两个链表有共同的交点pos1,现在求第一个交点  
  105.         int len1, len2;  
  106.         len1 = len2 = 0;  
  107.         Link *nd1, *nd2;  
  108.         nd1 = head1;  
  109.         while (nd1 != pos1) {len1++;nd1=nd1->next;}  
  110.         nd2 = head2;  
  111.         while (nd2 != pos1) {len2++;nd2=nd2->next;}  
  112.         // 较长链表的链表的nd先走dif步  
  113.         int dif;  
  114.         nd1 = head1; nd2 = head2;  
  115.         if (len1 >= len2)  
  116.         {  
  117.             dif = len1 - len2;  
  118.             while (dif--) nd1 = nd1->next;  
  119.         }  
  120.         else  
  121.         {  
  122.             dif = len2 - len1;  
  123.             while (dif--) nd2 = nd2->next;  
  124.         }  
  125.         // 之后两个nd再一起走,直到nd相等(即为第一个交点)  
  126.         while (nd1!=pos1 && nd2!=pos1)  
  127.         {  
  128.             if (nd1 == nd2)  
  129.                 return nd1;  
  130.             nd1 = nd1->next;  
  131.             nd2 = nd2->next;  
  132.         }  
  133.         return pos1;  
  134.     }  
  135. }  
  136.   
  137. int total[] = {8, 2, 5};  
  138. int C[10] = {15, 14, 13, 12, 11, 10, 9, 8};  
  139. int B[10] = {7, 6};  
  140. int A[10] = {5, 4, 3, 2, 1};  
  141.   
  142. int main()  
  143. {  
  144.     Link *headB, *headA;  
  145.     headB = headA = NULL;  
  146.     int i;  
  147.     for (i=0; i<total[0]; i++)  
  148.         insertNode(headB, C[i]);  
  149.     Link *nd = headB;  
  150.     while (nd->next) nd = nd->next;  
  151.     // 8->9->10->11->12->13->14->15->10->11->12->...  
  152.     nd->next = headB->next->next;  
  153.     headA = headB;  
  154.     // B: 6->7->8->9->10->...->15->10->...  
  155.     for (i=0; i<total[1]; i++)  
  156.         insertNode(headB, B[i]);  
  157.     // A: 1->2->3->4->5->8->9->10->...->15->10->...  
  158.     for (i=0; i<total[2]; i++)  
  159.         insertNode(headA, A[i]);  
  160.     // find: 8  
  161.     Link *pos = findFirstCross(headA, headB);  
  162.     if (pos)  
  163.         printf("yes: %d\n", pos->data);  
  164.     else  
  165.         printf("no\n");  
  166. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值