Java 算法篇-深入了解单链表的反转(实现:用 5 种方式来具体实现)

🔥博客主页: 小扳_-CSDN博客
❤感谢大家点赞👍收藏⭐评论✍
  

 

 

 

文章目录

        1.0 单链表的反转说明

        2.0 单链表的创建

        3.0 实现单链表反转的五种方法

        3.1 实现单链表反转 - 循环复制(迭代法)

        3.2 实现单链表反转 - 头插法

        3.3 实现单链表反转 - 递归法

        3.4 实现单链表反转 - 三指针法

        3.5 实现单链表反转 - 第二种头插法

        4.0 实现单链表反转的五种完整代码


        1.0 单链表的反转说明

        单链表的反转是指将链表中的节点顺序逆转,即原先的链表尾部变成了头部,头部变成了尾部。比如,[1,2,3,4,5,6,7] 将这个链表的值反转得到的结果为:[7,6,5,4,3,2,1],需要注意的是,可以用值打印出来会更好观察链表反转后的结果。

        2.0 单链表的创建

        具体的创建思路:首先要把节点进行封装成单独的类,该类中的成员包括:int value 存放值Node next 引用下一个节点。由于该节点类为链表中的一个组成部分,因此,将该类设为静态内部类,外部类的成员为哨兵节点

代码如下:

import java.util.Iterator;public class Linked implements Iterable<Integer>{private final Node hand;private int size;static class Node {public int value;public Node next;public Node(int value, Node next) {this.value = value;this.next = next;}}public Linked() {hand = new Node(0,null);}public void addLast (int value) {Node p = hand;while (p.next != null) {p = p.next;}p.next = new Node(value, null);size++;}public void addFirst(int value) {hand.next = new Node(value,hand.next);size++;}@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {Node p = hand.next;@Overridepublic boolean hasNext() {return p != null;}@Overridepublic Integer next() {int k = p.value;p = p.next;return k;}};}}

        为了方便对反转的五种方法进行讲解,实现了以上的头插节点尾插节点用迭代器循环节点的值。

本篇重点是反转的五种的方法,所以对以上的实现的 API 不太了解的话,点击以下链接去了解一下:   Java 数据结构篇-实现单链表核心API-CSDN博客

        3.0 实现单链表反转的五种方法

        迭代法:遍历链表,依次改变节点的指针方向,使其指向前一个节点。

        递归法:利用递归函数,先反转后续节点,然后再将当前节点的指针指向前一个节点。

        头插法:从头到尾遍历原链表,依次将每个节点插入到新链表的头部,形成新的反转链表。

        :利用栈的特性,将链表节点依次入栈,然后依次出栈构建新的反转链表。

        三指针法:使用三个指针分别指向当前节点、前一个节点和后一个节点,依次改变节点的指针方向,实现链表的反转。

        3.1 实现单链表反转 - 循环复制(迭代法

        思路为:遍历旧的链表来取得每个节点的对应的值,然后新链表根据得到的值来创建节点进行头插节点,这样就实现了链表反转了。需要注意的是,这个方法并没有改变旧链表

代码如下:

    //方法一: 循环复制public Linked reverseMethod1 (Linked linked) {Linked linked1 = new Linked();for (Integer integer : linked) {linked1.addFirst(integer);}return linked1;}

        分析以上代码:先创建一个新链表 linked1 然后调用头插节点的方法,通过传入旧节点的值来创建对象,也就是节点。需要注意的是,这里的增强 for 循环是已经实现了。

        3.2 实现单链表反转 - 头插法

        通过改变旧节点的节点指向来实现链表反转,当反转结束之后,旧的链表顺序就会被改变。具体思路:先要对旧节点的 hand.next 头节点从该链表独立出来,对于旧链表来说就是删除头节点,不过要记录要删除的节点,得到这个头节点之后,该节点头插到新的链表中,一直循环往复下去,直到 hand.next == null 结束循环。

        对于这个链表来说,这就可以把 remove 这个节点删除了,也为头删节点。当 hand.next = null 就说明了没有节点了,就应该结束循环了。

        这就是头插节点的流程。

代码如下:

    //方法二: 改变旧节点的指向public Linked reverseMethod2(Linked linked) {Linked linkedNew = new Linked();Node handNew = linkedNew.hand;while (hand.next != null) {//先头删Node temp = linked.hand.next;linked.hand.next = temp.next;//再头插Node tp = handNew.next;handNew.next = temp;temp.next = tp;}return linkedNew;}

对以上代码进行分析:

         先创建一个新的链表,在删除头节点前,需要将要删除的节点记录起来,如果一个对象没被引用就会被 JVM 自动回收,然后头节点指向删除节点的指向下一个的节点。头插节点前,需要将 hand.next 同样要记录,然后头节点指向新的节点,新的节点的下一个节点,正就是原先的 hand.next

        3.3 实现单链表反转 - 递归法

         思路:通过递归这种方式,先递出到 “底” ,得到旧链表的最后一个节点,所以递归结束的条件也就是 temp.next == null,返回该节点 temp 也就是最后的节点。递归的函数也很容易可以想出来为: 调用自己的方法名(node.next) 。

代码如下:

    //方法三: 递归public Linked reverseMethod3(Linked linked) {Linked linked1 = new Linked();linked1.hand.next = reversion(linked.hand.next);return linked1;}public Node reversion (Node node) {if (node == null || node.next == null) {return node;}Node last = reversion(node.next);node.next.next = node;node.next = null;return last;}

        对以上代码进行分析:先创建一个新的链表 linkded1 ,调用 reversion()  这个方法来得到头节点。具体来看是如何得到头节点,显而易见,就是通过递归,递到底(到尽头)取到最后一个节点,然后将这个尾节点一直返回出来,OK,这里就拿到了新链表的头节点了。接下来要考虑的是,如何将剩下的节点反转呢?

 先来看看递归的流程图:

     

分析如下:  

        假设有五个节点,在递归回归的过程中,需要做的事第五个节点指向第四个节点,第四个节点指向第三个节点...一直下去,这样是不是就实现了每个节点指向都反转了。但是需要注意的是,当第二个节点指向第一个节点,这里都没有问题,我们很容易会忽略,第一个节点原先就指向第二个节点,因此,会出现死循环,解决办法:先暂时将被指向的节点赋值为:null ,这样就完美解决了。

        3.4 实现单链表反转 - 三指针法

        实现思路:在原先链表中进行改变每个节点的引用,先定义出三个变量分别为 o1,o2,n1,一开始的时候 o1 , n1指向链表中的 hand 头节点(先来搞定没有哨兵的链表),对于 o2 指向 hand.next 。接下来就可以开始移动 o2,n1 这个两个指针了,主要的是 o1 保持不变,永远指向 hand 节点,先让 hand.next = o2.next ,这个意思就是将 hand.next 的头节点的下一个节点从链表中移出,然后将移出来的节点指向 n1 ,最后 o2 = n1 将 n1 赋值给 o2 ,o2 再接着指向 hand.next 的节点

几个简单的过程:

代码如下:

    //方法四:双引用public Linked reverseMethod4(Linked linked) {linked.hand.next = dualPointers(linked.hand.next);return linked;}public Node dualPointers(Node hand) {Node o1 = hand;Node n1 = hand;Node o2 = o1.next;while (o2 != null) {o1.next = o2.next;o2.next = n1;n1 = o2;o2 = o1.next;}return n1;}

        通过以上的简单几个过程且结合代码来分析,应该会有一点点感觉,来总结以下,对于 o1 的动作就是站在 hand 节点中 "一动不动", 对于 o2 可以将它看作搬运工,不断搬运 hand.next 的节点运到 n1 节点的前面(n1 的左边),具体就是先要将 o2.next 节点被 hand.next 引用,接着就是搬运了, o2.next 指向 n1 的节点,然后 n1 需要往后跳一格(往左边移动一个节点)即将 o2 赋值给 n1,然后 o2 就返回去指向 o1.next 的节点了,循环往复下去,直到当 o1.next == null 时,就该结束循环了

        3.5 实现单链表反转 - 第二种头插法

        思路:遍历旧链表的每一个节点,然后直接插入到新链表中,这个方法个第一种头插很相似,区别就是第一种头插的方法是面向对象编程的,第二种头插的方法是面向过程来编程的。

代码如下:

    //方法五:public Linked reverseMethod5(Linked linked) {Node o1 = linked.hand.next;Linked linked1 = new Linked();Node n1 = null;while (o1 != null) {Node temp = o1.next;o1.next = n1;n1 = o1;o1 = temp;}linked1.hand.next = n1;return linked1;}

        这里也运用到了头删还有头插,跟之前的头插有所不同,这里的头插是将插入进来的节点变成为新链表的头节点,直到遍历结束为止。

        4.0 实现单链表反转的五种完整代码

import java.util.Iterator;public class Linked implements Iterable<Integer>{public Node hand;private int size;static class Node {public int value;public Node next;public Node(int value, Node next) {this.value = value;this.next = next;}}public Linked() {hand = new Node(0,null);}public void addLast (int value) {Node p = hand;while (p.next != null) {p = p.next;}p.next = new Node(value, null);size++;}public void addFirst(int value) {hand.next = new Node(value,hand.next);size++;}@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {Node p = hand.next;@Overridepublic boolean hasNext() {return p != null;}@Overridepublic Integer next() {int k = p.value;p = p.next;return k;}};}//方法一: 循环复制public Linked reverseMethod1 (Linked linked) {Linked linked1 = new Linked();for (Integer integer : linked) {linked1.addFirst(integer);}return linked1;}//方法二: 改变旧节点的指向public Linked reverseMethod2(Linked linked) {Linked linkedNew = new Linked();Node handNew = linkedNew.hand;while (hand.next != null) {//先头删Node temp = linked.hand.next;linked.hand.next = temp.next;//再头插Node tp = handNew.next;handNew.next = temp;temp.next = tp;}return linkedNew;}//方法三: 递归public Linked reverseMethod3(Linked linked) {Linked linked1 = new Linked();linked1.hand.next = reversion(linked.hand.next);return linked1;}public Node reversion (Node node) {if (node == null || node.next == null) {return node;}Node last = reversion(node.next);node.next.next = node;node.next = null;return last;}//方法四:双引用public Linked reverseMethod4(Linked linked) {linked.hand.next = dualPointers(linked.hand.next);return linked;}public Node dualPointers(Node hand) {Node o1 = hand;Node n1 = hand;Node o2 = o1.next;while (o2 != null) {o1.next = o2.next;o2.next = n1;n1 = o2;o2 = o1.next;}return n1;}//方法五:public Linked reverseMethod5(Linked linked) {Node o1 = linked.hand.next;Linked linked1 = new Linked();Node n1 = null;while (o1 != null) {Node temp = o1.next;o1.next = n1;n1 = o1;o1 = temp;}linked1.hand.next = n1;return linked1;}}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/141143.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot+vue的学生毕业离校信息网站

项目介绍 该学生毕业离校系统包括管理员、学生和教师。其主要功能包括管理员&#xff1a;首页、个人中心、学生管理、教师管理、离校信息管理、费用结算管理、论文审核管理、管理员管理、留言板管理、系统管理等&#xff0c;前台首页&#xff1b;首页、离校信息、网站公告、留…

python 根据经纬度绘制点图 极投影

参考了python cartopy手动导入地图数据绘制底图/python地图上绘制散点图&#xff1a;Downloading:warnings/散点图添加图里标签_python add_feature-CSDN博客 点的颜色按照时间显示 # -*- coding: utf-8 -*- """ Created on Mon Nov 13 11:32:48 2023"&quo…

智慧工地源码:助力数字建造、智慧建造、安全建造、绿色建造

智慧工地围绕建设过程管理&#xff0c;建设项目与智能生产、科学管理建设项目信息生态系统集成在一起&#xff0c;该数据在虚拟现实环境中&#xff0c;将物联网收集的工程信息用于数据挖掘和分析&#xff0c;提供过程趋势预测和专家计划&#xff0c;实现工程建设的智能化管理&a…

postgresql安装

postgresql安装 1、windows下的安装 安装包下载地址&#xff1a;https://www.enterprisedb.com/downloads/postgres-postgresql-downloads 这里选择14.5版本进行安装&#xff0c;具体的安装步骤&#xff1a; 双击软件打开运行&#xff0c;弹出如下界面&#xff1a; 点击nex…

CountDownLatch使用

常用于多线程场景&#xff0c;待多线程都结束后方可继续主线程逻辑处理 CodeConstant 常量类 import java.util.HashMap; import java.util.Map;public class CodeConstant {public static final Map<String, Map<String, String>> CODE new HashMap<>();…

博弈论入门

目录 什么是博弈&#xff1f; 博弈论的发展历史&#xff1f; 博弈的要素有哪些&#xff1f; 博弈的分类&#xff1f; 博弈论的应用 收益矩阵 纳什均衡的定义 博弈论的例子 1、田忌赛马 2、穷途困境 2.1优化反应函数法 2.2Nashpy库 2.3顶点枚举算法 3、Nash游戏 …

飞书开发学习笔记(五)-Python快速开发网页应用

飞书开发学习笔记(五)-Python快速开发网页应用 一.下载示例代码 首先进入飞书开放平台: https://open.feishu.cn/app 凭证与基础信息 页面&#xff0c;在 应用凭证 中获取 App ID 和 App Secret 值。 教程和示例代码位置:https://open.feishu.cn/document/home/integrating-…

WebSocket在node端和客户端的使用

摘要 如果想要实现一个聊天的功能&#xff0c;就会想到使用WebSocket来搭建。那如果没有WebSocet的时候&#xff0c;我们会以什么样的思路来实现聊天功能呢&#xff1f; 假如有一个A页面 和 B页面进行通信&#xff0c;当A发送信息后&#xff0c;我们可以将信息存储在文件或者…

JDK更换版本不生效问题

JDK版本更换 问题: 当本地电脑拥有多个版本jdk时, 切换jdk版本不生效 解决方案: 1.查看环境变量(高版本的jdk安装时自动注入环境变量) 2.将Path里面的jdk的bin配置上移到最上面 3.查看jdk版本, java -version 启动项目,显示如下使用了jdk17

Adobe premiere裁剪视频尺寸并转为GIF格式

第 1 步&#xff1a;裁剪视频 修改序列设置以适应裁剪之后的图像区域&#xff1b;序列中的编辑模式不能使用默认的&#xff0c;这里使用的是“ProRes RAW” 第 2 步&#xff1a;设置背景色 需要设置“颜色遮罩”的大小和颜色&#xff0c;颜色遮罩放在下面。 第 3 步&#xff1…

el-form添加自定义校验规则校验el-input只能输入数字

0 效果 1 代码 {1,5}是用来限制小数点后几位的 addFormRules: {investAmount: [{ validator: checkInvestAmount, trigger: blur }], }, const checkInvestAmount (rule, value, callback) > {if (value ! && value ! null && value ! undefined) {if (/…

SSL证书对于SEO优化的重要性

SEO是指通过优化网站的内容、结构和技术元素以提升其在搜索引擎结果页上的排名的过程。其中一个经常被忽视但对SEO效果产生深远影响的因素就是SSL证书。 SSL证书是一种数字证书&#xff0c;用于加密网站流量并验证网站身份。它有助于保护用户的敏感数据&#xff0c;并在访问者和…

Django 基于ORM的CURD、外键关联,请求的生命周期

文章目录 基于ORM进行的CURDORM外键关联Django请求的生命周期流程图 基于ORM进行的CURD 本质上就是通过面向对象的方式&#xff0c;对数据库的数据进行增、删、改、查。 这里将会将我们之前所有内容结合到一起&#xff0c;首先确保基于上序操作已经建立好了UserInfo表&#xff…

【BMC】jsnbd介绍

jsnbd介绍 本文主要介绍一个名为jsnbd的开源项目&#xff0c;位于GitHub - openbmc/jsnbd&#xff0c;它实现了一个前端&#xff08;包含HTML和JS文件&#xff09;页面&#xff0c;作为存储服务器&#xff0c;可以指定存储内容&#xff1b;还包含一个后端的代理&#xff0c;这…

【计算机网络】VRRP协议理论和配置

目录 1、VRRP虚拟路由器冗余协议 1.1、协议作用 1.2、名词解释 1.3、简介 1.4、工作原理 1.5、应用实例 2、 VRRP配置 2.1、配置命令 2.2、拓扑与配置&#xff1a; 1、VRRP虚拟路由器冗余协议 1.1、协议作用 虚拟路由冗余协议(Virtual Router Redundancy Protocol&am…

【C++入门篇】保姆级教程篇【下】

目录 一、运算符重载 1&#xff09;比较、赋值运算符重载 2&#xff09; 流插入留提取运算符重载 二、剩下的默认成员函数 1&#xff09;赋值运算符重载 2&#xff09;const成员函数 3&#xff09;取地址及const取地址操作符重载 三、再谈构造函数 1&#xff09;初始化列表 …

C#源代码生成器深入讲解一

C#源代码生成器 01 源代码生成器初体验 新建一个类库&#xff0c;一定是standard2.0版本&#xff0c;否则会出问题。引用Nuget包Microsoft.CodeAnalysis.Common新建一个类&#xff0c;继承自ISourceGenerator接口 //一定要写&#xff0c;制定语言 [Generator(LanguageNames.…

4.2每日一题(求多元函数在某一点的微分)

1、分别求x和y的偏导&#xff0c;再相加即可 2、因为多元函数的表达式不方便求偏导&#xff0c;所以可以使用先代后求法&#xff1a; &#xff08;1&#xff09;对x偏导&#xff1a;把y0代入&#xff0c;很容易求出对x偏导的结果 &#xff08;2&#xff09;对y偏导&#xff1a…

caffe搭建squeezenet网络的整套工程

之前用pytorch构建了squeezenet&#xff0c;个人觉得pytorch是最好用的&#xff0c;但是有的工程就是需要caffe结构的&#xff0c;所以本篇也用caffe构建一个squeezenet网络。 数据处理 首先要对数据进行处理&#xff0c;跟pytorch不同&#xff0c;pytorch读取数据只需要给数据…

帝国cms中如何让外部链接直接从新窗口打开页面

<?php if($bqr[isurl]) { ?> <a href"<?$bqsr[titleurl]?>" target"_blank"> <?php } else { ?> <a href"<?$bqsr[titleurl]?>"> <?php } ?>