判断两个单链表是否相交同时找出第一个相交点(单链表是否有环及寻找入口节点)

      这个问题,首先应判断两个单链表是否含有环,判断是否有环的方法很多,目前最好的方法是:一开始设置两个指针都指向表头,其中一个每次(一步)前进一个节点的叫p1,另外那个每次(一步)前进两个节点的叫p2 。p1和p2同时走,当其中有一个遇到null,就证明链表没有环。如何某个时刻(假设走了n步之后),p1和p2指向的地址相同,那么链表就是有环的。

      方法代码如下:

	public boolean isLoop(Node h){
		Node p1 = h;
		Node p2 = h;
		while(p2.next != null && p2.next.next != null){
			p1 = p1.next;
			p2 = p2.next;
			if(p1 == p2) break;
		}
		return !(p1==null||p2==null);
	}

      这样,就判断出两个链表是否存在环,有以下3种情况,一、两链表均不含环,此时为情况一;二、两链表均含环,此时为情况二;三,一个有环一个无环,显然两链表此时不可能相交,该情况不考虑。

      一、均无环的情况下判断是否相交,且找出第一个相交点

      无环情况下,判断是否相交,可以让两个链表均遍历至尾节点,如果尾节点相同,则说明两链表是有交点的,代码如下:

	/*
	 * 无环情况下判断是否相交 两个链表无环时若相交,则至少尾节点必然是同一个
	 */
	public boolean isJointNoLoop(Node h1, Node h2) {
		Node p = h1;
		Node q = h2;
		while (p.next != null) {
			p = p.next;
		}
		while (q.next != null) {
			q = q.next;
		}
		return p == q;
	}

      此时已判断出是否相交,相交后,来寻找第一个相交点,此时有两种思路:方法一:较易懂,算出两个链表的长度差δ,两个指针从表头出发,之后较长的链表先移动δ步,之后两链表同时移动,直到遇到相同的节点,该节点即为第一个相交点,代码如下:

	/*
	 * 无环情况下找出第一个相交点
	 */
	public Node getFirstJoint(Node h1, Node h2) {
		int len1 = 0;
		int len2 = 0;
		while (h1.next != null) {
			len1++;
			h1 = h1.next;
		}
		while (h2.next != null) {
			len2++;
			h2 = h2.next;
		}
		return len1-len2>=0?getNode(h1,h2,len1,len2):getNode(h2, h1, len2, len1);
	}

	private Node getNode(Node h1, Node h2, int len1, int len2) {
		int i = 0;
		while (i < len1 - len2) {
			h1 = h1.next;
		}
		while (true) {
			h1 = h1.next;
			h2 = h2.next;
			if (h1 == h2)
				return h1;
		}
	}

         方法二:将其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在(该环就是首尾相连的链表),则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。需找出环的入口,设置p1,p2两个指针,同样一个走一步一个走两步,两者相遇则必在环上某一点相遇,记下此位置p1=p2,在p1和p2重合后,设置一个p3指向表头,然后p1和p3每次同时行走一步,每步前进一个节点,等到p1和p3重合时,重合的位置就是环的入口。

      可以这样理解,如图:


      设L1为无环长度,L2为环长,a为两指针相遇时慢速指针在环上走过的距离,而且a一定小于环总长L2(这是因为当慢速指针刚进入环时,快速指针已经在环中,且距离慢速指针的距离最长为L2-1,需要追赶的距离为L2-1,即刚好在慢速指针的下一个节点,需要几乎一整圈的距离来追赶,赶上时,慢速指针也不能走完一圈)。此时设慢速指针走过的节点数为N,则可列出:

快速指针走过的节点数为: 2N  =  L1 + k * L2 + a;   (这里快速指针走过的节点数一定是慢速指针走过的2倍)。

慢速指针走过的节点数为:  N  =  L1 + a;

则相减可得, N  =  k * L2  ,    于是得到  k * L2 = L1 + a;  即, L1 = (k-1) * L2 + (L2 - a)     (这里k至少是大于等于1的,因为快速指针至少要多走一圈)

即  L1的长度 = 环长的整数倍 + 相遇点到入口点的距离, 此时设置头结点p3, 与p1同时,每次都走一步,相遇点即为入口点。

整个过程代码如下:

	/*
	 * 相交的两个不含环单链表的第一个交点,方法一:将其中一个链表首尾相接,此时可看作一个含环单链表,找出环入口点即可
	 */
	public Node entryNoLoop(Node h1, Node h2) {
		Node p = h1;
		while (p.next != null) {
			p = p.next;
		}
		// 此时p指向链表1的尾节点,首尾相连
		p.next = h1;
		return entryLoop(h2);
	}

	/*
	 * 找出一个含环单链表的环入口点
	 */
	public Node entryLoop(Node h) {
		Node p3 = h;
		Node p1 = h;
		Node p2 = h;
		while (p2.next != null && p2.next.next != null) {
			p1 = p1.next;
			p2 = p2.next.next;
			if (p1 == p2) {
				break;
			}
		}
		while (p3 != p1) {
			p1 = p1.next;
			p3 = p3.next;
		}
		return p3;
	}

      二、均有环的情况下,判断是否相交

      思路与无环时的思路较类似,二者均有环,那么可以找出各自环的入口点,然后从各自的入口点出发,一个快速(每次2步),一个慢速(每次1步),直到相遇,说明相交。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值