第 7 题(链表)
微软亚院之编程判断俩个链表是否相交
给出俩个单向链表的头指针,比如 h1,h2,判断这俩个链表是否相交。
为了简化问题,我们假设俩个链表均不带环。
问题扩展:
1.如果链表可能有环列?
2.如果需要求出俩个链表相交的第一个节点列?
看到这个题目我很困惑。如果链表的结构是下面这个样子
typedef struct ListNode { int m_Value; ListNode * p_Next; }ListNode;
那么一旦有相交,链表的后端就都是一模一样的了啊?因为交叉点只可能有一个p_Next,那只要判断两个链表最后一个不为空的结点是否相同就可以了。
对有环列的,那环只能出现在链表最后面了。扩展的第1、2问只要对两个链表做两层循环判断就可以了。
但总觉得我理解的不对。对于带环链表的创建我也有点困惑。
在网上看了看,我的理解居然是对的。 我判断环的方法是用一个指针数组缓存已经出现过的点,比较新的点在之前是否出现过。 对于带环的相交判断是分开环的部分和无环的部分,先判断没有环的部分是否相交,如果不相交,再判断一个环结点是否在另一个环中出现。
缺点:需要数组来缓存,链表最大长度有限制。
/* 第 7 题(链表) 微软亚院之编程判断俩个链表是否相交 给出俩个单向链表的头指针,比如 h1,h2,判断这俩个链表是否相交。 为了简化问题,我们假设俩个链表均不带环。 问题扩展: 1.如果链表可能有环列? 2.如果需要求出俩个链表相交的第一个节点列? start time = 11:10 end time = */ #include <stdio.h> #include <stdlib.h> typedef struct ListNode { int m_Value; ListNode * p_Next; }ListNode; //无环 bool isCross(ListNode * p1, ListNode * p2) { ListNode * x = p1; ListNode * y = p2; while(x->p_Next != NULL) { x = x->p_Next; } while(y->p_Next != NULL) { y = y->p_Next; } return (x == y); } //判断是否有环 若有返回环的交叉结点 没有返回NULL ListNode * isCircle(ListNode * pHead) { if(pHead == NULL) return NULL; ListNode * p[100]; int n = 0; p[n] = pHead; n++; while(p[n - 1]->p_Next != NULL) { p[n] = p[n - 1]->p_Next; n++; for(int i = 0; i < n - 1; i++) { if(p[i] == p[n - 1]) { return p[i]; } } if(n >= 100) { printf("input is too large"); return NULL; } } return NULL; } ListNode * isCrossWithCircle(ListNode * p1, ListNode *p2) { ListNode * c1 = isCircle(p1); ListNode * c2 = isCircle(p2); if(c1 == NULL && c2 == NULL) //都没有环 { c1 = p1; c2 = p2; while(c1 != NULL) { while(c2 != NULL) { if(c1 == c2) return c1; c2 = c2->p_Next; } c1->p_Next; } } else if(c1 != NULL && c2 != NULL) //都有环 { ListNode * h1 = p1; ListNode * h2 = p2; while(h1 != c1) //判断非环部分是否相交 { while(h2 != c2) { if(h1 == h2) { return h1; } h2 = h2->p_Next; } h1 = h1->p_Next; } h1 = c1; h2 = c2; do //判断非环部分是否相交 第一个公共点是 两个交叉点中的任意一个 { if(h1 == h2) { return h1; } h1 = h1->p_Next; }while(h1 != c1); } return NULL; } int main() { ListNode n1 , n2, n3, n4, n5, n6, n7, n8, n9; n1.m_Value = 1; n1.p_Next = &n2; n2.m_Value = 2; n2.p_Next = &n3; n3.m_Value = 3; n3.p_Next = &n7; n4.m_Value = 4; n4.p_Next = &n5; n5.m_Value = 5; n5.p_Next = &n6; n6.m_Value = 6; n6.p_Next = &n8; n7.m_Value = 7; n7.p_Next = &n8; n8.m_Value = 8; n8.p_Next = &n9; n9.m_Value = 9; n9.p_Next = &n7; //bool flag = isCross(&n1, &n4); ListNode * a = isCrossWithCircle(&n1, &n4); return 0; }
网上发现,判断是否有环和环结点有专门的方法:
http://www.cppblog.com/humanchao/archive/2008/04/17/47357.aspx
一、判断链表是否存在环,办法为:
设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表)程序如下:
bool IsExitsLoop(slist *head) { slist *slow = head, *fast = head; while ( fast && fast->next ) { slow = slow->next; fast = fast->next->next; if ( slow == fast ) break; } return !(fast == NULL || fast->next == NULL); }
二、找到环的入口点
当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)循环内环+相遇点到环入口点,于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。程序描述如下:
slist* FindLoopPort(slist *head) { slist *slow = head, *fast = head; while ( fast && fast->next ) { slow = slow->next; fast = fast->next->next; if ( slow == fast ) break; } if (fast == NULL || fast->next == NULL) return NULL; slow = head; while (slow != fast) { slow = slow->next; fast = fast->next; } return slow; }
判断两个单链表是否相交,如果相交,给出相交的第一个点(两个链表都不存在环)。
比较好的方法有两个:
一、将其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在,则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。
二、如果两个链表相交,那个两个链表从相交点到链表结束都是相同的节点,我们可以先遍历一个链表,直到尾部,再遍历另外一个链表,如果也可以走到同样的结尾点,则两个链表相交。
这时我们记下两个链表length,再遍历一次,长链表节点先出发前进(lengthMax-lengthMin)步,之后两个链表同时前进,每次一步,相遇的第一点即为两个链表相交的第一个点。
看过答案后修改代码:
/* 第 7 题(链表) 微软亚院之编程判断俩个链表是否相交 给出俩个单向链表的头指针,比如 h1,h2,判断这俩个链表是否相交。 为了简化问题,我们假设俩个链表均不带环。 问题扩展: 1.如果链表可能有环列? 2.如果需要求出俩个链表相交的第一个节点列? start time = 11:10 end time = */ #include <stdio.h> #include <stdlib.h> typedef struct ListNode { int m_Value; ListNode * p_Next; }ListNode; ListNode * isCircle(ListNode * pHead) //判断是否有环,返回环的起始点指针 { ListNode * slow = pHead; ListNode * fast = pHead; while(fast != NULL && fast->p_Next != NULL) { slow = slow->p_Next; fast = fast->p_Next->p_Next; if(slow == fast) break; } if(fast == NULL || fast->p_Next == NULL) return NULL; slow = pHead; while(slow != fast) { slow = slow->p_Next; fast = fast->p_Next; } return slow; } ListNode * isCross(ListNode * p1, ListNode * p2) { ListNode * c1 = isCircle(p1); ListNode * c2 = isCircle(p2); if(c1 == c2) //都不是带环的 或者有环且环相同 只可能在环结点前面相交 { ListNode * h1 = p1; ListNode * h2 = p2; int len1 = 0; int len2 = 0; while(h1->p_Next != c1) //统计从起点到相同点有多少步,以及c1 c2之前的那个结点 { len1++; h1 = h1->p_Next; } while(h2->p_Next != c2) { len2++; h2 = h2->p_Next; } if(h1 == h2) //无环且相交 或 有环但相交点在非环部分 { h1 = p1; h2 = p2; if(len1 > len2) //长度长的链先走多出来的部分,剩下的同步走,直到相同 { for(int i = 0; i < len1 - len2; i++) { h1 = h1->p_Next; } } else { for(int i = 0; i < len2 - len1; i++) { h2 = h2->p_Next; } } while(h1 != h2) { h1 = h1->p_Next; h2 = h2->p_Next; } return h1; } else if(c1 != NULL) //恰在环交点相交 { return c1; } } else if(c1 != NULL && c2 != NULL) //如果两个环不同 { ListNode * h1 = c1; //判断在环1内能否找到环2的点 do { if(h1 == c2) return h1; h1 = h1->p_Next; }while(h1 != c1); } return NULL; //其他情况均不会相交 } int main() { ListNode n1 , n2, n3, n4, n5, n6, n7, n8, n9; n1.m_Value = 1; n1.p_Next = &n2; n2.m_Value = 2; n2.p_Next = &n3; n3.m_Value = 3; n3.p_Next = &n7; n4.m_Value = 4; n4.p_Next = &n5; n5.m_Value = 5; n5.p_Next = &n6; n6.m_Value = 6; n6.p_Next = &n8; n7.m_Value = 7; n7.p_Next = &n8; n8.m_Value = 8; n8.p_Next = &n9; n9.m_Value = 9; n9.p_Next = &n7; ListNode * a = isCross(&n1, &n4); return 0; }