单链表有环求长度及相交问题

单链表的节点只有一个指针指向下一个节点,两个单链表相交的话就会导致两个单链表的尾节点是相同的,所以只需要比较尾节点是否相同就可以知道两个单链表是否相交。但是,这样是否就完全没有问题了呢?

环!

其实不然,单链表有可能没有尾节点!为什么?因为有可能存在环!如果存在环的话上面那个办法就行不通了!考虑到因为环的存在,原来单链表的尾节点消失了,既然因为环没有尾节点了,那我们就创造一个尾节点!

假设两个单链表分别为A和B,第一步,遍历A,使用两个指针,一个指针每一步移动一个节点,另一个指针每一步移动两个节点(当然这里的一个节点和两个节点可以替换为别的数目,只要保证两个数的最大公约数为1就可以了,因为这个可以保证两个指针在环中一定会相遇)。使用两个指针遍历链表A,如果某一步两个指针相等,那么说明存在环,就将这一点作为A的尾节点,如果不存在环,我们最终也能获得A的尾节点。第二步,遍历链表B,并将每一个节点和A的尾节点相比较,如果存在相同的节点,那么说明两个链表相交,否则不相交。使用这种方法我们最终能在O(n+m)(n为A的长度,m为B的长度)的时间复杂度内解决问题。

环的长度怎么求?

如果知道链表A有环,那么怎么求出A的环的长度呢?这个问题其实非常简单!上文中已经描述了找到链表A的环中的一个节点,也就是那个尾节点,只要绕环一周便可以计算出环的 长度。

环的第一个节点?

如果链表有环,怎么求环的第一个节点呢?我在查阅资料的时候发现有这样一个定理:对于有环的单链表A,使用一个每次走一个节点的指针P和一个每次走两个节点的指针K遍历链表A,当指针P和K第一次碰撞时,此时用一个每次走一个节点的指针H从单链表头开始遍历,当指针P和H相遇时一定位于环的第一个节点!使用数学方法可以很容易来证明这个定理。设链表的节点为o1,o2,o3,……,oN,i1,i2,……,iK,……,iM其中o1是链表的第一个节点,oN是环的第一个节点,iK是指针P和K发生碰撞的节点(指针P和K发生碰撞的节点是唯一的),iM的next指针指向oN。当指针P和K第一次相遇时,此时指针P走过了N+K个节点,而指针K走过了2N+2K个节点,可以得到这样一个等式:N+K = N+((N+2K) mod (M+1)),化简可得:0 = (N+K) mod (M+1)!这个化简后的等式说明,指针P走过N个节点会处于oN节点,而指针H开始指向链表头,它走过N个节点也会位于oN节点,虽然不知到N究竟是多少,但是指针P和H同步走一定会相交于oN,而且相交的第一个位置就是oN。

链表的长度怎么求?

有了上一段的分析,链表的长度就容易得到了!有环的链表,其长度可以分为两部分来求,一部分是环外,一部是环!如果将无环链表看作是环长度为0的链表,则可以得到一个统一的求解链表长度的代码,见行93到125,其中,行99到106计算得到iK,行107到115计算环的长度,行116到122计算环外节点数目。

两个单链表的交点怎么求?

如果单链表不存在环,求解交点有两种比较好的方法,第一种方法是将链表尾节点指向其中一个链表的头节点,这样求交点的问题就转换成了求环的第一个节点问题,第二种方法效率更高一点,首先求出链表A和B的长度LA和LB,假设LA大于LB,将每次移动一个节点的指针PA指向链表A的第LA-LB+1个节点,将每次移动一个节点的指针PB指向链表B的第一个节点,然后同步移动指针PA和PB,两个指针相同时所指向的节点便是A和B的交点。如果单链表存在环的话,求解交点就麻烦一点,这里提一个比较简单的办法,对于链表A/B,取环上的第一个节点,将该节点指向链表B/A的头节点,这样得到一个新的有环链表,分别求解环的第一个节点,可以得到两个节点,如果两个节点相同,说明链表A和B的交点在环外,否则这两个链表在环外没有交点,这两个节点分别是链表A和B在环上的第一个节点!

下面贴上相关代码!

#include <iostream>
using namespace std;

struct list {
	int value;
	struct list *next;
};

struct list *insert(struct list *head, int v)
{
	if (head) {
		head->next = insert(head->next, v);
	} else {
		head = new struct list;
		head->value = v;
		head->next = 0;
	}
	return head;
}

void set_next_node(struct list *node, struct list *next)
{
	if (node) {
		node->next = next;
	}
}

struct list *find(struct list *head, size_t idx)
{
	while (idx-- && head && head->next) {
		head = head->next;
	}
	return head;
}

struct list *first_node(struct list *head1, struct list *head2)
{
	struct list *tail1, *next1 = 0;
	struct list *cursor1 = head1, *cursor2 = head1;
	do {
		if (cursor2) {
			cursor2 = cursor2->next;
			if (cursor2)
				cursor2 = cursor2->next;
		}
		tail1 = cursor1;
		next1 = cursor1->next;
		cursor1 = cursor1->next;
	} while (cursor1 != cursor2);
	cursor2 = head1;
	do {
		tail1 = cursor1;
		next1 = cursor1->next;
		cursor2 = cursor2->next;
		if (cursor1)
			cursor1 = cursor1->next;
	} while (cursor1 != cursor2);

	if (cursor1) {
		tail1 = cursor1;
		next1 = cursor1->next;
	}
	tail1->next = head2;

	cursor1 = head1, cursor2 = head1;
	do {
		if (cursor2) {
			cursor2 = cursor2->next;
			if (cursor2)
				cursor2 = cursor2->next;
		}
		cursor1 = cursor1->next;
	} while (cursor1 != cursor2);
	do {
		if (cursor2) {
			cursor2 = cursor2->next->next;
		}
		if (cursor1) {
			cursor1 = cursor1->next;
		}
	} while (cursor1 != cursor2);
	cursor2 = head1;
	do {
		cursor2 = cursor2->next;
		if (cursor1)
			cursor1 = cursor1->next;
	} while (cursor1 != cursor2);
	tail1->next = next1;

	return cursor1;
}

size_t length(struct list *head)
{
	size_t l = 0;
	if (head) {
		struct list *cursor1 = head;
		struct list *cursor2 = head;
		do {
			if (cursor2) {
				cursor2 = cursor2->next;
				if (cursor2)
					cursor2 = cursor2->next;
			}
			cursor1 = cursor1->next;
		} while (cursor1 != cursor2);
		do {
			if (cursor2) {
				cursor2 = cursor2->next->next;
			}
			if (cursor1) {
				l++;
				cursor1 = cursor1->next;
			}
		} while (cursor1 != cursor2);
		cursor2 = head;
		do {
			cursor2 = cursor2->next;
			if (cursor1)
				cursor1 = cursor1->next;
			l++;
		} while (cursor1 != cursor2);
	}
	return l;
}

void print_length(struct list *head, size_t l)
{
	cout << "list: ";
	while (head && l--) {
		cout << head->value;
		if (head->next) {
			if (l)
				cout << "->";
			else
				cout << "->[" << head->next->value <<"]";
		}
		head = head->next;
	}
	cout << endl;
}

void print(struct list *head)
{
	print_length(head, length(head));
}

int main()
{
	struct list *head1 = 0;
	struct list *head2 = 0;
	head1 = insert(head1, 0);
	head1 = insert(head1, 1);
	head1 = insert(head1, 2);
	head1 = insert(head1, 3);
	head1 = insert(head1, 4);
	head1 = insert(head1, 5);
	head1 = insert(head1, 6);
	head1 = insert(head1, 7);
	head1 = insert(head1, 8);
	head1 = insert(head1, 9);
	head2 = insert(head2, 10);
	head2 = insert(head2, 11);
	head2 = insert(head2, 12);
	head2 = insert(head2, 13);
	set_next_node(find(head1, -1), find(head1, 4));
	set_next_node(find(head2, -1), find(head1, 6));
	print(head1);
	print(head2);
	cout << length(head1) << endl;
	cout << length(head2) << endl;
	cout << "node: " << first_node(head1, head2)->value << endl;
	cout << "node: " << first_node(head2, head1)->value << endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值