判断链表是否相交的几种算法

     这个是《编程之美》里面的一个题目,给出两个单项链表的头指针,h1、h2判断这2个链表是否相交?

   【解法一】直观的想法

    循环遍历h1的每一个节点,判断是否存在一个节点在h2中,由于链表无法随机访问,每次查找需要对链表h2遍历最多length(h1)次,因此算法的时间复杂度是O(length(h1)*length(h2)),显然这个方法很耗时间。

    【解法二】利用hash计数

     ①循环遍历h1,计算每个节点的hash值并存入map中;②循环遍历h2,顺序计算每个节点的hash值v,并用map.get(v),若返回非空,则算法结束。

     第①步算法时间复杂度O(length(h1)),第②步算法时间复杂度O(length(h2)),因此hash计数 算法时间复杂度为O(max(length(h1),length(h2))),复杂度降低到线性。但是由于使用了额外的map结构,空间复杂度为O(length(h1))。

     【解法三】链表连接

       我们分析两个单链表相交时,节点的逻辑结构如下:

    h1->P1->P2->...->Pi->...->K1->...->Km

    h2->Q1->Q2->...->Qi->...->K1->...->Km

    可以看到如果把h1链表的尾节点的next指针指向h2链表的第一个节点,那么可以看到如果h1和h2相交,则

h2变成了一个循环单链表,因此只需判断h2是否为循环链表即可。需要遍历h1和h2,因此算法时间负责度O(max(length(h1),length(h2))),空间负责度为O(1)。但是这个算法的缺点是需要链接h1和h2,改变链表的逻辑结构,在多线程环境下需要上锁,影响一定的性能。

     【解法四】更简单的做法,直接判断链表末节点是否相同

 

    同解法三的节点分析,如果h1和h2相交于节点K1,那么根据单链表的定义(任何节点有且仅有唯一的后继和唯一的前驱,null也算作前驱和后继吧),因此如果h1和h2相交,那么两个链表从K1以后的后继的节点是相同的。因此判断链表是否相交,只用判断尾节点是否相同即可,只需遍历2个链表,时间复杂度为O(max(length(h1),length(h2))),空间复杂度为O(1)。这个算法很简单优雅,赞一个。

      发散思维:

      1、如何求解两个相交链表的第一个相交节点?

      这个其实也很简单,使用小学数学知识即可解决。

      记 L1 = length(h1); L2 = length(h2);  h1和h2 由于相交,共有K个节点相同,那么两个链表的长度之差为相交节点之前的节点之差。因此我们让长链表先遍历abs(L1-L2)步,然后让两个链表同时遍历,同时判断2个链表的节点是否相同,若存在相同节点,则返回该节点,算法结束。

      2、如果两个链表中本身存在环,该如何解决?

      存在环的单链表,类似于这种形式。

    

      如果链表存在环,则上面的算法都无法返回正确结果,陷入死循环,因此首先要把环解开。下一篇日志将介绍几种检测链表是否存在环的算法。

       

package com.xx.dataStructure;

import java.util.HashMap;
import java.util.Map;

//节点定义
class Node {
	int data;
	Node next;
	
	Node (int data,Node next){
		this.data = data;
		this.next = next;
	}
	
	@Override
	public int hashCode(){
		return data ;
	}
	
	@Override
	public boolean equals(Object o){
		if (o == null) 
			return false;
		if (this == o)
			return true;
		if (o instanceof Node){
			Node node = (Node)o;
			return this.data == node.data;
		}
		return false;
	}
}

//使用策略模式
interface Intersect {
	boolean isIntersect(Node h1,Node h2);
}

abstract class AbstractIntersect implements Intersect{
	protected String name ;
	
	public String getName(){
		return name;
	}
}
//循环遍历法
class FullTraverse extends AbstractIntersect{
	{
		name = " FullTraverse algorithm ";
	}
	
	@Override
	public boolean isIntersect(Node h1, Node h2) {
		if (h1 == null || h2 == null) 
			return false;
		Node i = h1.next;
		Node j = h2.next;
		for(;i != null ; i = i.next ){
			for(j = h2.next; j != null ; j = j.next){
				if (j == i)
					return true;
			}
		}
		return false;
	}
}

//hash计数法
class HashCalculate extends AbstractIntersect {
	{
		name = " HashCalculate algorithm ";
	}
	
	@Override
	public boolean isIntersect(Node h1, Node h2) {
		if (h1 == null || h2 == null) 
			return false;
		//init map
		Map<Integer,Node> map = new HashMap<Integer,Node>(0);
		Node i = h1.next;
		while(i != null){
			map.put(i.hashCode(), i);
			i = i.next;
		}
		//find the same node 
		Node j = h2.next;
		while(j != null){
			if (map.get(j.hashCode()) != null) 
				return true;
			j= j.next;
		}
		return false;
	}
}

//首尾相连法
class LinkHeadAndTail extends AbstractIntersect {
	
	{
		name = " LinkHeadAndTail algorithm ";
	}
	
	@Override
	public boolean isIntersect(Node h1, Node h2) {
		if (h1 == null || h2 == null) 
			return false;
		boolean result = false;
		Node i = h1.next;
		Node j = h2.next;
		while(i != null && i.next != null){
			i = i.next;
		}
		i.next = j;
		boolean begin = true;
		//判断h2是否为循环链表
		while(j != null ){
			if (j == h2.next && !begin ){
				result = true;
				break;
			}
			else {
				j = j.next;
				begin = false;
			}
		}
		//链表解除锁定
		i.next = null;
		return result;
	}
}

//尾节点判定法
class TailEqual extends AbstractIntersect {
	
	{
		name = " TailEqual algorithm ";
	}
	
	@Override
	public boolean isIntersect(Node h1, Node h2) {
		if (h1 == null || h2 == null) 
			return false;
		Node tail1 = h1.next;
		Node tail2 = h2.next;
		while(tail1 != null && tail1.next != null){
			tail1 = tail1.next;
		}
		while(tail2 != null && tail2.next != null){
			tail2 = tail2.next;
		}
		if (tail1 == tail2 )
			return true;
		return false;
	}
}

public class LinkedList {
	//相交节点
	static Node intersectNode = new Node(8,new Node(9,new Node(10,null)));
	//h1链表  0->1->2->8->9->10
	static Node h1 = new Node(-1,new Node(0,new Node(1,new Node(2,intersectNode))));
	//h2链表 0->11->12->13->14->8->9->10
	static Node h2 = new Node(-1,new Node(10,new Node(11,new Node(12,new Node(13,new Node(14,intersectNode))))));
	
	static Node h3 = new Node(-1,new Node(0,new Node(1,null)));
	static Node h4 = new Node(-1,new Node(10,null));
	
	static Node h5 = null;
	static Node h6 = null;
	
	static Node h7 = new Node(-1,new Node(0,null));
	static Node h8 = new Node(-1,null);
	
	public static void main(String [] args){
		AbstractIntersect[] methods = {
				new FullTraverse(),
				new HashCalculate(),
				new LinkHeadAndTail(),
				new TailEqual()
		};
		
		for(AbstractIntersect method : methods){
			System.out.println(method.getName() + ":"+  method.isIntersect(h1, h2));
			System.out.println(method.getName() + ":"+  method.isIntersect(h3, h4));
			System.out.println(method.getName() + ":"+  method.isIntersect(h5, h6));
			System.out.println(method.getName() + ":"+  method.isIntersect(h7, h8));
		}
		
	}
}

 

 

     参考书籍:《编程之美》

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值