1. 程式人生 > >程式設計之美3.6——程式設計判斷兩個連結串列是否相交

程式設計之美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