Java单链表反转图文详解
Java单链表反转图文详解
最近在回顾链表反转问题中,突然有一些新的发现和收获,特此整理一下,与大家分享
背景回顾
单链表的存储结构如图:
数据域存放数据元素,指针域存放后继结点地址

我们以一条 N1 -> N2 -> N3 -> N4 指向的单链表为例:

反转后的链表指向如图:

我们在代码中定义如下结点类以方便运行测试:
/**
* 结点类
* (因为后续在main方法中运行,为了方便定义为static内部类)
*/
static class Node {
int val; // 数据域
Node next; // 指针域,指向下一个结点
Node(int x, Node nextNode) {
val = x;
next = nextNode;
}
}
通过循环遍历方式实现链表反转
实现思路:从链表头结点出发,依次循环遍历每一个结点,并更改结点对应的指针域,使其指向前一个结点
代码如下:
/**
* 循环遍历方式实现链表反转
*
* @param head 链表的头结点
* @return
*/
public static Node cycleNode(Node head) {
Node prev = null; // 保存前一个结点的信息
// 循环遍历链表中的结点
while (head.next != null) {
// 1. 先保存当前结点的下一个结点的信息到tempNext
Node tempNext = head.next;
// 2. 修改当前结点指针域,使其指向上一个结点(如果是第一次进入循环的头结点,则其上一个结点为null)
head.next = prev;
// 3. 将当前结点信息保存到prev中(以作为下一次循环中第二步使用到的"上一个结点")
prev = head;
// 4. 当前结点在之前的123步中指针域已经修改完毕,此时让head重新指向待处理的下一个结点
head = tempNext;
}
// 上面的循环完成后,实际只修改了原先链表中的头结点到倒数第二个结点间的结点指向,倒数第一个结点(尾结点)并未处理
// 此时prev指向原先链表中的倒数第二个结点,head指向尾结点
// 处理尾结点的指针域,使其指向前一个结点
head.next = prev;
// 返回尾结点,此时的尾结点既是原先链表中的尾结点,又是反转后的新链表中的头结点
return head;
}
测试效果:
public static void main(String[] args) {
// 构造测试用例,链表指向为 N1 -> N2 -> N3 -> N4
Node n4 = new Node(4, null);
Node n3 = new Node(3, n4);
Node n2 = new Node(2, n3);
Node n1 = new Node(1, n2);
Node head = n1;
// 输出测试用例
System.out.println("原始链表指向为:");
printNode(head);
// 普通方式反转链表
System.out.println("循环方式反转链表指向为:");
head = cycleNode(head);
printNode(head);
}
/**
* 循环打印链表数据域
* @param head
*/
public static void printNode(Node head) {
while (head != null) {
System.out.println(head.val);
head = head.next;
}
}
运行结果如图:

可以看到,原先指向为 N1 -> N2 -> N3 -> N4 的链表,运行反转方法后,其指向已变为 N4 -> N3 -> N2 -> N1
通过递归方式实现链表反转
实现思路:从链表头结点出发,依次递归遍历每一个结点,并更改结点对应的指针域,使其指向前一个结点(没错,实际每一次递归里的处理过程跟上面的循环里是一样的)
代码实现:
/**
* 递归实现链表反转
* 递归方法执行完成后,head指向就从原链表顺序:头结点->尾结点 中的第一个结点(头结点) 变成了反转后的链表顺序:尾结点->头结点 中的第一个结点(尾结点)
*
* @param head 头结点
* @param prev 存储上一个结点
*/
public static void recursionNode(Node head, Node prev) {
if (null == head.next) {
// 设定递归终止条件
// 当head.next为空时,表明已经递归到了原链表中的尾结点,此时单独处理尾结点指针域,然后结束递归
head.next = prev;
return;
}
// 1. 先保存当前结点的下一个结点的信息到tempNext
Node tempNext = head.next;
// 2. 修改当前结点指针域,使其指向上一个结点(如果是第一次进入递归的头结点,则其上一个结点为null)
head.next = prev;
// 3. 将当前结点信息保存到prev中(以作为下一次递归中第二步使用到的"上一个结点")
prev = head;
// 4. 当前结点在之前的123步中指针域修改已经修改完毕,此时让head重新指向待处理的下一个结点
head = tempNext;
// 递归处理下一个结点
recursionNode(head, prev);
}
测试效果:
public static void main(String[] args) {
// 构造测试用例,链表指向为 N1 -> N2 -> N3 -> N4
Node n4 = new Node(4, null);
Node n3 = new Node(3, n4);
Node n2 = new Node(2, n3);
Node n1 = new Node(1, n2);
Node head = n1;
// 输出测试用例
System.out.println("原始链表指向为:");
printNode(head);
// 递归方式反转链表
System.out.println("递归方式反转链表指向为:");
recursionNode(head, null);
printNode(head);
}
/**
* 循环打印链表数据域
* @param head
*/
public static void printNode(Node head) {
while (head != null) {
System.out.println(head.val);
head = head.next;
}
}
注意:在上面的测试代码中,在调用递归函数时传递了Node类的实例head作为参数
根据Java中 方法调用传参中,基本类型是值传递,对象类型是引用传递 可得 =>
因为在调用递归函数时传递了head对象的引用,且在递归函数运行过程中,我们已经数次改变了head引用指向的对象,
那么当递归函数执行完毕时,head引用指向的对象此时理论上已经是原链表中的尾结点N4了,且链表顺序也已经变成了 N4 -> N3 -> N2 -> N1
运行效果截图:

最终的程序运行结果与我的设想大相径庭!
那么,问题出在哪里呢?
递归方式反转链表问题排查与延伸
问题定位
既然程序运行效果与预期效果不符,那我们就在head对象引用可能发生变化的地方加入注释打印一下对象地址,看看能不能发现问题在哪:
加入注释后的代码如下:
public static void main(String[] args) {
// 构造测试用例,链表指向为 N1 -> N2 -> N3 -> N4
Node n4 = new Node(4, null);
Node n3 = new Node(3, n4);
Node n2 = new Node(2, n3);
Node n1 = new Node(1, n2);
Node head = n1;
// 输出测试用例
System.out.println("原始链表指向为:");
printNode(head);
// 递归方式反转链表
System.out.println("递归方式反转链表指向为:");
System.out.println("递归调用前 head 引用指向对象: " + head.toString());
recursionNode(head, null);
System.out.println("递归调用后 head 引用指向对象: " + head.toString());
printNode(head);
}
/**
* 循环打印链表数据域
* @param head
*/
public static void printNode(Node head) {
while (head != null) {
System.out.println(head.val);
head = head.next;
}
}
/**
* 递归实现链表反转
* 递归方法执行完成后,head指向就从原链表顺序:头结点->尾结点 中的第一个结点(头结点) 变成了反转后的链表顺序:尾结点->头结点 中的第一个结点(尾结点)
*
* @param head 头结点
* @param prev 存储上一个结点
*/
public static void recursionNode(Node head, Node prev) {
System.out.println("递归调用中 head引用指向对象: " + head.toString());
if (null == head.next) {
// 设定递归终止条件
// 当head.next为空时,表名已经递归到了原链表中的尾结点,此时单独处理尾结点指针域,然后结束递归
head.next = prev;
System.out.println("递归调用返回前 head引用指向对象: " + head.toString());
return;
}
// 1. 先保存当前结点的下一个结点的信息到tempNext
Node tempNext = head.next;
// 2. 修改当前结点指针域,使其指向上一个结点(如果是第一次进入循环的头结点,则其上一个结点为null)
head.next = prev;
// 3. 将当前结点信息保存到prev中(以作为下一次递归中第二步使用到的"上一个结点")
prev = head;
// 4. 当前结点在之前的123步中指针域修改已经修改完毕,此时让head重新指向待处理的下一个结点
head = tempNext;
// 递归处理下一个结点
recursionNode(head, prev);
}
运行结果:

从上面的运行结果看,在递归函数执行期间,head引用指向的对象确实发生了变化
注意 调用前 / 调用返回前 / 调用后 这三个地方head引用指向对象的变化:

可以发现,虽然递归函数执行期间确实改变了head引用指向的对象,但实际上是变了个寂寞!
Java单链表反转图文详解的更多相关文章
- Java单链表反转 详细过程
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/guyuealian/article/details/51119499 Java单链表反转 Java实 ...
- java 单链表反转
最近与人瞎聊,聊到各大厂的面试题,其中有一个就是用java实现单链表反转.闲来无事,决定就这个问题进行一番尝试. 1.准备链表 准备一个由DataNode组成的单向链表,DataNode如下: pub ...
- java单链表反转
今天做leetcode,遇到了单链表反转.研究了半天还搞的不是太懂,先做个笔记吧 参考:http://blog.csdn.net/guyuealian/article/details/51119499 ...
- Java Stream函数式编程图文详解(二):管道数据处理
一.Java Stream管道数据处理操作 在本号之前发布的文章<Java Stream函数式编程?用过都说好,案例图文详解送给你>中,笔者对Java Stream的介绍以及简单的使用方法 ...
- java单链表反转(花了半个多小时的作品)
欢迎光临............... 首先我们要搞清楚链表是啥玩意儿?先看看定义: 讲链表之前我们先说说Java内存的分配情况:我们new对象的时候,会在java堆中为对象分配内存,当我们调用方法的 ...
- Centos 7 进入单用户模式图文详解
由于昨晚做了一个很傻X的事情,所以有幸进入了CentOS 7 的单用户模式. CentOS 7 在进入单用户的时候和6.x做了很多的改变, 下面让我们来看看如何进入单用户模式. 如何进入CentOS ...
- 【图文详解】scrapy安装与真的快速上手——爬取豆瓣9分榜单
写在开头 现在scrapy的安装教程都明显过时了,随便一搜都是要你安装一大堆的依赖,什么装python(如果别人连python都没装,为什么要学scrapy….)wisted, zope interf ...
- 单链表反转(Singly Linked Lists in Java)
单链表反转(Singly Linked Lists in Java) 博客分类: 数据结构及算法 package dsa.linkedlist; public class Node<E> ...
- Java WebService接口生成和调用 图文详解>【转】【待调整】
webservice简介: Web Service技术, 能使得运行在不同机器上的不同应用无须借助附加的.专门的第三方软件或硬件, 就可相互交换数据或集成.依据Web Service规范实施的应用之间 ...
随机推荐
- CSS Multiple Columns
CSS Multiple Columns CSS layout column-count column-gap column-rule-style column-rule-width column-r ...
- js bese64转化为blob使用FormData上传
原文 工作示例 <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...
- Flutter:发布包
[package] 生成包含模块化Dart代码的可共享Flutter项目 [plugin] 生成一个可共享的Flutter项目, 在Dart代码中包含带有API的API, 针对Android的平台特定 ...
- 从微信小程序到鸿蒙js开发【06】——swiper&animator&marquee
目录: 1.swiper轮播图 2.image-animator幻灯片 3.marquee跑马灯 4.nginx动静分离 1.swiper轮播图 微信小程序的swiper组件中只能放置swiper-i ...
- SSL/TLS协议详解(中)——证书颁发机构
本文转载自SSL/TLS协议详解(中)--证书颁发机构 导语 上一篇中,我们讨论了关于Diffie Hellman算法的SSL/TLS密钥交换.我们最终认为需要第三方来验证服务器的真实性,并提出了证书 ...
- 详解Go语言调度循环源码实现
转载请声明出处哦~,本篇文章发布于luozhiyun的博客: https://www.luozhiyun.com/archives/448 本文使用的go的源码15.7 概述 提到"调度&q ...
- Vue学习笔记-nodejs+vue-cli4+webpack按装配置+项目创建
一 使用环境: windows 7 64位操作系统 二 Vue学习-nodejs按装配置,Node.js 就是运行在服务端的 JavaScript. 1. 去nodejs的官网下载 https: ...
- 微信小程序:Navigator导航组件
导航组件:类似超链接标签. url:要跳转的页面路径,可以放绝对路径,也可以放相对路径,绝对路径指从pages作为根目录开始找到你要的页面. 找到你要找的页面的相对地址的方法:在vscode中,该页面 ...
- 免费的图片校正及漂白专业工具PicGrayRemover 0.96,专业去除文档图片黑底麻点杂色,还你一个清晰的文本。
当家长多年,每天都要拍照试卷打印.用App去掉图片黑底就成了每天必备工作.可是,有些图片文件不是来自手机,所以需要一个电脑版的图片漂白工具. 因此,经过几周的努力,图片漂白工具 PicGrayRemo ...
- 设置ViewPager 自动滑动时间,速度 方便展示动画
ViewPager.setCurrentItem(position),即使已设置动画,但是没有动画效果 原因:因为ViewPager滑动之前的时间间隔太短,可以通过反射,去修改ViewPager自动滑 ...