`

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

 
阅读更多

     这个是《编程之美》里面的一个题目,给出两个单项链表的头指针,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));
		}
		
	}
}

 

 

     参考书籍:《编程之美》

  

  • 大小: 1.7 KB
分享到:
评论

相关推荐

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

    可以看到如果把h1链表的尾节点的next指针指向h2链表的第一个节点,那么可以看到如果h1和h2相交,则h2变成了一个循环单链表,因此只需判断h2是否为循环链表

    算法导论(part2)

    书中每一章都给出了一个算法、一种算法设计技术、一个应用领域或一个相关的主题。算法是用英语和一种“伪代码”来描述的,任何有一点程序设计经验的人都能看得懂。书中给出了230多幅图,说明各个算法的工作过程。...

    算法导论(part1)

    书中每一章都给出了一个算法、一种算法设计技术、一个应用领域或一个相关的主题。算法是用英语和一种“伪代码”来描述的,任何有一点程序设计经验的人都能看得懂。书中给出了230多幅图,说明各个算法的工作过程。...

    基于c++数字逻辑电子仿真器

    然后调用IsInArea(point)函数来判断当前点point是否在以当前触点中心点为中心的矩形区域内。如果是,则用一个全局枚举变量put来记录来前触点是两个输入端和一个输出端中哪一个。 我们看这个枚举类型: enum Myput { ...

    第五次作业函数第一题代码

    第五次作业函数第一题--

    基于深度学习的作物病害诊断内含数据集和运行环境说明.zip

    本项目旨在利用深度学习方法实现作物病害的自动诊断。作物病害是农业生产中的重要问题,及时诊断和处理对于减少产量损失至关重要。 我们采用深度学习算法,通过分析作物的图像,实现对病害的自动识别和分类。项目使用的数据集包括公开的作物病害图像数据集,如ISIC等,并进行了预处理,包括图像增强、分割和特征提取等。 在运行环境方面,我们使用Python编程语言,基于TensorFlow、PyTorch等深度学习框架进行开发。为了提高计算效率,我们还使用了GPU加速计算。此外,我们还采用了Docker容器技术,确保实验结果的可重复性。 项目完成后,将实现对作物病害的快速、准确诊断,为农业生产提供有力支持,有助于减少产量损失。同时,项目成果也可应用于其他图像识别和分类任务。

    机械设计CD驱动印刷设备step非常好的设计图纸100%好用.zip

    机械设计CD驱动印刷设备step非常好的设计图纸100%好用.zip

    tensorflow-2.7.2-cp37-cp37m-manylinux2010-x86-64.whl

    python烟花代码

    python烟花代码示例

    附件中是一个简单的烟花效果的代码示例: 在Python中,可以使用多种方式来模拟烟花效果,其中一种常用的方法是使用turtle模块,它提供了一个画布和一个小海龟,可以用来绘制各种图形。 这段代码首先导入了turtle模块和random模块,然后在屏幕上绘制了10次烟花爆炸的效果。每次爆炸都是由5个小圆组成,颜色随机选择,圆的大小也是随机的。 请注意,这段代码需要在支持turtle模块的Python环境中运行,并且需要有图形界面的支持。如果你在没有图形界面的环境中(比如某些服务器或者命令行界面),这段代码可能无法正常运行。

    商业化产品经理,到底如何实现产品商业化?.docx

    商业化产品经理,到底如何实现产品商业化?.docx

    Panduit 工业以太网部件内部销售指南

    Panduit 工业以太网部件内部销售指南

    Java版三维装箱代码示例

    在Java中,实现一个三维装箱(也称为三维背包问题)的算法通常涉及到组合优化和动态规划。这个问题是一个典型的优化问题,其中目标是在三个维度的限制下最大化价值的总和。下面是一个简单的Java代码示例,它使用动态规划来解决三维装箱问题。 请注意,这个代码只是一个简单的示例,它假设所有物品的第三个维度的大小都是1,并且没有给出如何回溯选择物品的完整逻辑。在实际应用中,三维装箱问题可能更加复杂,需要考虑所有三个维度的限制,并且可能需要更复杂的算法来解决。 此外,这个问题的解决方案可能需要根据具体问题的要求进行调整,例如物品是否可以分割、是否允许超过一个的物品等。如果你有特定的问题描述或者需要进一步的帮助,请提供更多的细节。

    常用品牌EPLAN部件库

    常用品牌EPLAN部件库

    单片机开发的教程.doc

    单片机开发的教程可以分为以下几个步骤: 1. 了解单片机基础知识:在学习单片机开发之前,需要了解单片机的相关知识,包括单片机的基本结构、指令系统、编程语言等。 2. 选择开发板:选择一款适合自己学习开发板的型号和厂商,通常需要关注开发板的性价比、开发环境是否友好等因素。 3. 学习开发环境:根据所选的开发板,学习相关的开发环境和使用方法,例如Keil、IAR等集成开发环境。 4. 掌握编程语言:单片机常用的编程语言包括C语言和汇编语言,根据实际情况选择其中一种进行学习。 5. 基础操作:熟悉单片机的引脚定义和IO口配置,了解单片机的启动代码,可以通过修改启动代码进行基本功能调试。 6. 综合实践:根据具体项目需求,进行单片机开发的综合实践。在实践中需要掌握如何编写程序、如何进行硬件调试、如何使用相关工具软件等技能。 下面是一个单片机开发的简单教程介绍: 首先,确定所使用的单片机型号和开发板类型。在这个阶段,需要查阅相关资料,了解开发板的规格书、芯片规格等基本资料。 其次,安装并配置开发环境。根据所选的开发板,安装相应的集成开发环境(IDE),并配置好开发环境。 接着,学习并掌

    Q1.ipynb

    Q1.ipynb

    (自适应手机端)IT网络建站公司pbootcms模板 互联网营销企业网站源码下载.zip

    (自适应手机端)IT网络建站公司pbootcms模板 互联网营销企业网站源码下载.zip

    Bematech 激光扫描器用户手册

    Bematech 激光扫描器用户手册

    激励视频接入文档.pdf

    激励视频接入文档.pdf

    java jdk1.8 202版本下载window linux打包

    java jdk1.8 202版本下载window linux打包

    Lite Beam M5快速指南 Lite Beam M5天线设置指南

    Lite Beam M5快速指南

Global site tag (gtag.js) - Google Analytics