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规范实施的应用之间 ...
随机推荐
- LeetCode 题解 593. Valid Square (Medium)
LeetCode 题解 593. Valid Square (Medium) 判断给定的四个点,是否可以组成一个正方形 https://leetcode.com/problems/valid-squa ...
- website captcha
website captcha 验证码 hCaptcha hCaptcha通过询问对人类来说很容易且对机器来说很困难的简单问题,可以帮助您喜欢的Web服务阻止机器人,垃圾邮件和滥用行为. https: ...
- js function arguments types
js function arguments types https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functi ...
- NGK:APP一站式挖矿高收益项目
NGK是10月中旬刚上线的公链项目,采用手机挖矿形式.NGK数字增益平台,200美金即可入场,收益可观,分为静态和动态两种,投资算力收益超高.邀请好友挖矿还有额外的返佣. NGK立志为所有人创造无差别 ...
- 如何让别人访问我的电脑的vue项目
步骤: 1.关闭防火墙. 2.修改build/webpack.dev.conf.js中的"const HOST = process.env.HOST"为"const HO ...
- Linux安装ElasticSearch7.X & IK分词器
前言 安装ES之前,请先检查JDK版本,es使用java编写,强依赖java环境.JDK安装过程略. 安装步骤 1.下载地址 点击这里下载7.2.0 2.解压elasticsearch-7.2.0-l ...
- Java基本概念:异常
一.简介 描述: 异常(Exception)指不期而至的各种状况,异常发生的原因有很多,通常包含以下几大类: 用户输入了非法数据. 要打开的文件不存在. 网络通信时连接中断,或者JVM内存溢出. 异常 ...
- loadrunner学习笔记一
这篇笔记主要是针对一个具体的loadrunner脚本里面出现的方法进行解释,具体脚本如下: ` Action() { char *transactionName = "Test"; ...
- 微信小程序折线图表折线图加区域图
1.先来个效果图 这里我用的是插件@antv/f2-canvas(安装的方法如下) npm init 此处如果直接使用官方npm install 可能会出现没有node_modules错误 npm i ...
- 一个 java 文件的执行过程详解
平时我们都使用 idea.eclipse 等软件来编写代码,在编写完之后直接点击运行就可以启动程序了,那么这个过程是怎么样的? 总体过程 我们编写的 java 文件在由编译器编译后会生成对应的 cla ...