Java判断单链表是否有环的两种实现方法

Java判断单链表是否有环的两种实现方法Java判断单链表是否有环的两种实现方法

大家好,又见面了,我是你们的朋友全栈君。

package learn.linkTest;

import java.util.HashMap;

/**
 * 
 * @author liuxin
 * @date   2018年6月15日
 */


public class LinkLoop {
	private Node root;
	
	public static void main(String[] args) {
		//1-5创建一个无环链表
		Node n1 =new Node(1);
		Node n2 =new Node(2);
		Node n3 =new Node(3);
		Node n4 =new Node(4);
		Node n5 =new Node(5);
		//下面注释的两行为有环链表
//		Node n5 =new Node(1);
//		Node n6 =new Node(5);
		//指定每个节点的下一个节点的值
		n1.next=n2;
		n2.next=n3;
		n3.next=n4;
		n4.next=n5;
//		n5.next=n6;
//		n5.next=n1;
		
		System.err.println(hasLoop(n1));//打印结果是false表示是无环链表,ture是有环链表
		System.err.println(hasLoop2(n1));
		//遍历打印整个链表
		System.out.println(n1.name);
		/**
		 * 不是环时,可调用此方法
		 */
//		while(n1.next!=null){
//			System.out.println("-->"+n1.next.getName());
//			n1=n1.next;
//		}
	}
	
	public void add(int name){
		Node newNode=new Node(name);
		if(this.root==null){
			this.root=newNode;
		}else{
			this.root.addNode(newNode);
		}
	}
	
	public void print(){
		if(this.root!=null){
			this.root.printNode();
		}
	}
	
	/**
	 *方法一: 遍历链表,创建两个类似指针的变量,一个指针每次向后移动一个节点,一个指针每次移动两个节点,如果遇到两者相同的情况说明存在环
	 * @param n
	 * @return
	 */
	public static boolean hasLoop(Node n){
		Node tmp1=n;
		Node tmp2=n.next;
		while (tmp2!=null) {
			if(tmp1==tmp2)return true;
			tmp1=tmp1.next;
			tmp2=tmp2.next.next;
			if(tmp2==null)return false;
		}
		return true;
	}
	/**
	 * 方法二:把每个节点放到hash表中,因为存入的是对象,所有相同的就说明有环
	 * @param n
	 * @return
	 */
	public static boolean hasLoop2(Node n){
		Node tmp1=n;
		HashMap<Node, Node> ns	=new HashMap<Node,Node>();
		while(n!=null){
			if(ns.get(tmp1)!=null)return true;
			ns.put(tmp1, tmp1);
			tmp1=tmp1.next;
			if(tmp1==null)return false;
		}
		return true;
	}
	
	/**
	 * 自建内部类---链表类
	 *
	 * @author liuxin
	 * @date   2018年6月15日
	 */
	static class Node{
		private int name;//名字,用于标识每个节点的名称
		private Node next;//当前节点的下一个节点,也作为自身的一个属性
		public Node(int name){ //构造函数
			this.name = name;
		}
		public int getName(){
			return this.name;
		}
		/*
		 * 增加下一个节点,
		 */
		public void addNode(Node newNode){
			if(this.next == null){
				this.next=newNode;
			}else{
				this.next.addNode(newNode);
			}
		}
		
		public void printNode(){
			System.out.println(this.name+"-->");
			if(this.next!=null){
				this.next.printNode();
			}
		}
	};
		
}

方法一:首先从头节点开始,依次遍历单链表的每一个节点。每遍历到一个新节点,就从头节点重新遍历新节点之前的所有节点,用新节点ID和此节点之前所有节点ID依次作比较。如果发现新节点之前的所有节点当中存在相同节点ID,则说明该节点被遍历过两次,链表有环;如果之前的所有节点当中不存在相同的节点,就继续遍历下一个新节点,继续重复刚才的操作。

例如这样的链表:A->B->C->D->B->C->D, 当遍历到节点D的时候,我们需要比较的是之前的节点A、B、C,不存在相同节点。这时候要遍历的下一个新节点是B,B之前的节点A、B、C、D中恰好也存在B,因此B出现了两次,判断出链表有环。

假设从链表头节点到入环点的距离是D,链表的环长是S。D+S是链表总节点数。那么算法的时间复杂度是0+1+2+3+….+(D+S-1) = (D+S-1)*(D+S)/2 , 可以简单地理解成 O(N*N)。而此算法没有创建额外存储空间,空间复杂度可以简单地理解成为O(1)。

方法二:首先创建一个以节点ID为键的HashSet集合,用来存储曾经遍历过的节点。然后同样是从头节点开始,依次遍历单链表的每一个节点。每遍历到一个新节点,就用新节点和HashSet集合当中存储的节点作比较,如果发现HashSet当中存在相同节点ID,则说明链表有环,如果HashSet当中不存在相同的节点ID,就把这个新节点ID存入HashSet,之后进入下一节点,继续重复刚才的操作。

这个方法在流程上和方法一类似,本质的区别是使用了HashSet作为额外的缓存。

假设从链表头节点到入环点的距离是D,链表的环长是S。而每一次HashSet查找元素的时间复杂度是O(1), 所以总体的时间复杂度是1*(D+S)=D+S,可以简单理解为O(N)。而算法的空间复杂度还是D+S-1,可以简单地理解成O(N)。

方法三:首先创建两个指针1和2(在java里就是两个对象引用),同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针1每次向下移动一个节点,让指针2每次向下移动两个节点,然后比较两个指针指向的节点是否相同。如果相同,则判断出链表有环,如果不同,则继续下一次循环。

例如链表A->B->C->D->B->C->D,两个指针最初都指向节点A,进入第一轮循环,指针1移动到了节点B,指针2移动到了C。第二轮循环,指针1移动到了节点C,指针2移动到了节点B。第三轮循环,指针1移动到了节点D,指针2移动到了节点D,此时两指针指向同一节点,判断出链表有环。

此方法也可以用一个更生动的例子来形容:在一个环形跑道上,两个运动员在同一地点起跑,一个运动员速度快,一个运动员速度慢。当两人跑了一段时间,速度快的运动员必然会从速度慢的运动员身后再次追上并超过,原因很简单,因为跑道是环形的。

转自:https://blog.csdn.net/jq_ak47/article/details/52739651

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/106092.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 建立友好城市有什么用_缔结友好城市

    建立友好城市有什么用_缔结友好城市原题连接Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。输入格式第1行,一个整数N,表示城市数。第2行到第n+1行,每行两个整数,中间用1个空格隔开,分别表示南岸和

    2022年8月8日
    1
  • c#设计登录界面并添加数据库_windows窗体连接数据库

    c#设计登录界面并添加数据库_windows窗体连接数据库本篇文章介绍了C#窗体的数据库连接及登录功能的实现工具或平台:VS2010、sqlserver20121.创建完窗体后,点击数据,选择添加新数据源2.选择数据库3.选择数据集4.新建连接-MicrosoftSQLServer,添加完测试一下5.添加数据库-注意把连接字符串部分复制一下,一会儿要用的6.保存连接字符串到配置文

    2022年9月17日
    0
  • 历年数学界菲尔兹奖及其得主简介

    历年数学界菲尔兹奖及其得主简介菲尔兹奖及其得主简介菲尔兹奖是以已故的加拿大数学家、教育家J.C.菲尔兹(FieldS)的姓氏命名的。J.C.菲尔兹1863年5月14日生于加拿大渥太华。他11岁丧父、18岁丧母,家境不算太好,J.C.菲尔兹17岁进入多伦多大学攻读数学,24岁时在美国的约翰·霍普金斯大学获博士学位,26岁任美国阿格尼大学教授。1892年到巴黎、柏林学习和工作。1902年回国后执教于多伦多大学。1907年

    2022年5月16日
    158
  • nginx正向代理(超简单)

    正向代理是一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。环境192.168.153.179:正向代理192.168.153.178:客户端CentOSLinuxrelease7.5.1804(Core)关闭防火墙和selinux开始部署:首先,两台服务器安装nginx源码安装:1、安装启动安装依赖yum-yinstallwgetgcc

    2022年4月5日
    32
  • 流氓软件原理及防范

    流氓软件原理及防范本人不擅长表述,本章以问答形式进行1.我该怎么去寻找需要的软件?百度,Google,官网,以及一些熟知的网站,例如:脚本之家,吾爱激活成功教程,csdn,游侠,东坡下载等等,虽然有些网站一股浓浓的山寨感,但是却包含了大量的资源,比起不知名的网站,已经属于比较好的,并且部分网站社区的风格是由于建站时间较长,以前遗留的产物2.我需要注意什么?百度前两条很有可能是广告,并且由于百度竞价的存在,排在前面的不一定是想要的或最好的,没事多看几页注意文件大小,这里大小是真实下载时浏览器反馈的文件大小,是否符合常理

    2022年9月17日
    0
  • tkmapper教程_tkmapper

    tkmapper教程_tkmapperTKmapper初学springboot的集成,方式分为两大类:基于starter的自动配置基于@MapperScan注解的手工配置在starter的逻辑中,如果你没有使用@MapperScan注解,你就需要在你的接口上增加@Mapper注解,否则MyBatis无法判断扫描哪些接口。<dependency><groupId>tk.mybatis</groupId><artifactId>mapper-spri

    2022年10月7日
    1

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号