java中如何实现单链表反转

简介: 本文介绍了单向链表的创建及其反转的三种实现方法。首先,通过`DataNode`类构建了一个包含10个节点的单向链表,并提供了链表的打印功能。接着,分别使用递归、遍历和借助栈的方式实现了链表反转。递归方法简单但受限于栈深度(最大约12000个节点),遍历方法通用且效率最高,而借助栈的方法虽然易于理解但效率较低。通过对不同方法的时间性能测试,得出遍历方式在处理大规模数据时表现最佳。

1.准备链表

准备一个由DataNode组成的单向链表,DataNode如下:

csharp

代码解读

复制代码


public class DataNode {

	private int data;
	private DataNode next;
	public int getData() {
		return data;
	}
	public void setData(int data) {
		this.data = data;
	}
	public DataNode getNext() {
		return next;
	}
	public void setNext(DataNode next) {
		this.next = next;
	}
	public DataNode(int data) {
		this.data = data;
	}
}

构造链表

ini

代码解读

复制代码

public class DataChain {
	
	private  DataNode head;
	
	public DataChain(int size) {
		DataNode head = new DataNode(0);
		DataNode cur = head;
		for (int i = 1; i < size; i++) {
			DataNode tmp = new DataNode(i);
			cur.setNext(tmp);
			cur = tmp;
		}
		this.head = head;
	}

	public DataNode getHead() {
		return head;
	}

	public void setHead(DataNode head) {
		this.head = head;
	}

	public static void printChain(DataNode head) {
		StringBuilder sb = new StringBuilder();
		DataNode cur = head;
		sb.append(cur.getData());
		while (null != cur.getNext()) {
			sb.append(" -> ");
			sb.append(cur.getNext().getData());
			cur = cur.getNext();
		}
		System.out.println(sb.toString());
	}

	public static void main(String... strings) {
		DataChain chain = new DataChain(10);
		printChain(chain.getHead());
	}
}

运行main方法,即构造了一个包含10个node节点的单链表。

rust

代码解读

复制代码

#运行结果
0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9

2.通过递归实现单链表反转

考虑到代码的简洁性,首先考虑的是通过递归实现。

java

代码解读

复制代码

	/**
	 * 递归实现 当栈深度大于12000 则会出现StakOverflowError
	 * 
	 * @param head
	 * @return
	 */
	public static DataNode reverse1(DataNode head) {
		if (null == head || null == head.getNext())
			return head;
		DataNode revHead = reverse1(head.getNext());
		head.getNext().setNext(head);
		head.setNext(null);
		return revHead;
	}

以上即是递归实现的源码,但是需要考虑的问题是递归都在java栈中进行,需要考虑jdk支持的栈的深度。在jdk1.8.0_91版本中,当上述链表长度大于12000则会出现StackOverFlowError错误。说明对于该版本jdk栈的深度不能大于12000。

3.通过遍历实现

最通用的实现方式就是遍历。

ini

代码解读

复制代码

/**
	 * 遍历实现 通用实现方法
	 * 
	 * @param head
	 * @return
	 */
	public static DataNode reverse2(DataNode head) {
		if (null == head || null == head.getNext())
			return head;
		DataNode pre = head;
		DataNode cur = head.getNext();
		while (null != cur.getNext()) {
			DataNode tmp = cur.getNext();
			cur.setNext(pre);
			pre = cur;
			cur = tmp;
		}
		cur.setNext(pre);
		head.setNext(null);
		return cur;
	}

4.借助stack实现

考虑到stack具有先进后出这一特性,因此可以借助于stack数据结构来实现单向链表的反转。

ini

代码解读

复制代码

/**
	 * 方法3 利用其他数据结构 stack 
	 * @param head
	 * @return
	 */
	public static DataNode reverse3(DataNode head) {
		Stack<DataNode> stack = new Stack<DataNode>();
		for (DataNode node = head; null != node; node = node.getNext()) {
			stack.add(node);
		}
		DataNode reHead = stack.pop();
		DataNode cur = reHead;
		while(!stack.isEmpty()){
			cur.setNext(stack.pop());
			cur = cur.getNext();
			cur.setNext(null);
		}
		return reHead;
	}

上述实现方法在于操作简单,对于算法并不精通的同学可以尝试。缺点在于需要通过其他数据结构实现,效率会降低,至于效率会降低到什么程度,后面举例说明。

5.三种实现方式效率分析

ini

代码解读

复制代码

	public static void main(String... strings) {
		int size = 10;
		
		DataChain chain1 = new DataChain(size);
		printChain(chain1.getHead());
		long reverse1_start = System.currentTimeMillis();
		DataNode reNode1 = reverse1(chain1.getHead());
		long reverse1_cost = System.currentTimeMillis() - reverse1_start;
		printChain(reNode1);
		System.out.println("reverse1 cost time is ["+reverse1_cost+"]ms");
		
		DataChain chain2 = new DataChain(size);
		printChain(chain2.getHead());
		long reverse2_start = System.currentTimeMillis();
		DataNode reNode2 = reverse2(chain2.getHead());
		long reverse2_cost = System.currentTimeMillis() - reverse2_start;
		printChain(reNode2);
		System.out.println("reverse2 cost time is ["+reverse2_cost+"]ms");
		
		DataChain chain3 = new DataChain(size);
		printChain(chain3.getHead());
		long reverse3_start = System.currentTimeMillis();
		DataNode reNode3 = reverse3(chain3.getHead());
		long reverse3_cost = System.currentTimeMillis() - reverse3_start;
		printChain(reNode3);
		System.out.println("reverse3 cost time is ["+reverse3_cost+"]ms");
	}

执行结果:

rust

代码解读

复制代码

0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9
9 -> 8 -> 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
reverse1 cost time is [0]ms
0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9
9 -> 8 -> 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
reverse2 cost time is [0]ms
0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9
9 -> 8 -> 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
reverse3 cost time is [1]ms

在上述代码基础上,去掉打印输出,将size改为10000,结果如下:

css

代码解读

复制代码

reverse1 cost time is [1]ms
reverse2 cost time is [0]ms
reverse3 cost time is [6]ms

可以看出reverse2 明显优于其他两种实现方法。考虑到reverse1最多只支持12000,因此将size改为100000时,再观察reverse2和reverse3之间的执行结果:

css

代码解读

复制代码

reverse2 cost time is [6]ms
reverse3 cost time is [25]ms

因此可以看出,最好的方法是采用遍历的方式进行反转。


转载来源:https://juejin.cn/post/7131919862363848711

相关文章
|
3月前
|
算法 Java
Java语言实现链表反转的方法
这种反转方法不需要使用额外的存储空间,因此空间复杂度为,它只需要遍历一次链表,所以时间复杂度为,其中为链表的长度。这使得这种反转链表的方法既高效又实用。
331 0
|
Java 数据安全/隐私保护 Sentinel
面试官:Sentinel是如何实现限流的?
面试官:Sentinel是如何实现限流的?
1537 1
|
缓存 负载均衡 Dubbo
Sentinel 集群限流设计原理
Sentinel 集群限流设计原理
Sentinel 集群限流设计原理
|
4月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
838 0
|
消息中间件
RabbitMQ的死信队列和延时队列
RabbitMQ的死信队列和延时队列
|
9月前
|
存储 算法 Java
算法系列之递归反转单链表
递归反转链表的基本思路是将当前节点的next指针指向前一个节点,然后递归地对下一个节点进行同样的操作。递归的核心思想是将问题分解为更小的子问题,直到达到基本情况(通常是链表末尾)。
270 5
算法系列之递归反转单链表
|
canal 缓存 NoSQL
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
根据对一致性的要求程度,提出多种解决方案:同步删除、同步删除+可靠消息、延时双删、异步监听+可靠消息、多重保障方案
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
|
8月前
|
Java Spring
JDK动态代理和CGLIB动态代理的区别
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理: ● JDK动态代理只提供接口的代理,不支持类的代理Proxy.newProxyInstance(类加载器, 代理对象实现的所有接口, 代理执行器) ● CGLIB是通过继承的方式做的动态代理 , 如果某个类被标记为final,那么它是无法使用 CGLIB做动态代理的。Enhancer.create(父类的字节码对象, 代理执行器)
|
9月前
|
Java Maven 开发者
编写SpringBoot的自定义starter包
通过本文的介绍,我们详细讲解了如何创建一个Spring Boot自定义Starter包,包括自动配置类、配置属性类、`spring.factories`文件的创建和配置。通过自定义Starter,可以有效地复用公共配置和组件,提高开发效率。希望本文能帮助您更好地理解和应用Spring Boot自定义Starter,在实际项目中灵活使用这一强大的功能。
721 17
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
697 64