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

news2024/10/7 8:23:32

🔥博客主页: 小扳_-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++;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            Node p = hand.next;

            @Override
            public boolean hasNext() {

                return p != null;
            }

            @Override
            public 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++;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            Node p = hand.next;

            @Override
            public boolean hasNext() {

                return p != null;
            }

            @Override
            public 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.coloradmin.cn/o/1204469.html

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

相关文章

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

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

YOLOv5算法进阶改进(2)— 引入可变形卷积模块 | 涨点杀器

前言:Hello大家好,我是小哥谈。可变形卷积模块是一种改进的卷积操作,它可以更好地适应物体的形状和尺寸,提高模型的鲁棒性。可变形卷积模块的实现方式是在标准卷积操作中增加一个偏移量offset,使卷积核能够在训练过程中扩展到更大的范围,从而实现对尺度、长宽比和旋转等各…

Linux_磁盘管理_df命令

1、df命令是用来干什么的 df的全称是disk free&#xff0c;意为“磁盘空间”。 使用df命令可以查看系统中磁盘的占用情况&#xff0c;有哪些文件系统&#xff0c;在什么位置&#xff08;挂载点&#xff09;&#xff0c;总空间&#xff0c;已使用空间&#xff0c;剩余空间等。…

2023.11.13【读书笔记】丨生物信息学与功能基因组学(第六章 多重序列比对 下)

目录 6.4 多重序列比对数据库6.5 基因组区域的多重序列比对6.6 展望6.7 常见问题总结 6.4 多重序列比对数据库 Pfam&#xff1a;基于谱隐马尔可夫模型构建的蛋白质家族数据库 SMART&#xff1a;简易分子构型研究工具&#xff0c;与细胞信号传导、细胞外结构域以及染色质功能…

Jmeter+ant+Jenkins持续集成

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

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

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

2023年数维杯国际大学生数学建模挑战赛A题

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 cs数模团队在数维杯前为大家提供了许多资料的内容呀&#xff0…

java项目之公廉租房维保系统(ssm框架)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的公廉租房维保系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 一、业主管理功能 该部分内容提…

单电源供电的运放如何增加直流偏置

在一些单电源供电的运放电路中&#xff0c;输入信号可能是交流信号&#xff0c;有正也有负&#xff0c;如果输入信号直接接到运算放大电路&#xff0c;则运放不会输出负电压&#xff0c;只有正电压&#xff0c;从而不能实现信号的调理&#xff1b; 这时我们就需要给运放添加直流…

五分钟利用Vite创建Vue项目

1.准备工具 Vite是尤雨溪团队开发的&#xff0c;官方称是下一代新型前端构建工具&#xff0c;能够显著提升前端开发体验。 上面称是下一代&#xff0c;当前一代当然是我们熟悉的webpack Vite 优势 开发环境中&#xff0c;无需打包操作&#xff0c;可快速的冷启动。轻量快速…

Python 如何实现 Command(命令)模式?什么是 Command(命令)设计模式?

什么是命令设计模式&#xff1f; 命令模式&#xff08;Command Design Pattern&#xff09;是一种行为设计模式&#xff0c;它将请求封装成一个对象&#xff0c;从而允许参数化客户端对象&#xff0c;排队请求&#xff0c;或者对请求进行操作。命令模式支持撤销操作&#xff0…

ros1 基础学习07 - 模拟客户端生成小乌龟服务请求生成小乌龟

模拟客户端生成小乌龟服务请求生成小乌龟 一、话题模型二、创建功能包三 创建客户端Client代码四 配置CMakeLists.txt编译规则&#xff1a;五 测试启动ros 主服务启动小乌龟的服务启动模型客户端服务 一、话题模型 Sever端是海龟仿真器/turtlesim&#xff0c;Client端是待实现…

基于鸡群算法优化概率神经网络PNN的分类预测 - 附代码

基于鸡群算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于鸡群算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于鸡群优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

SPSS时间序列分析:序列图

前言&#xff1a; 本专栏参考教材为《SPSS22.0从入门到精通》&#xff0c;由于软件版本原因&#xff0c;部分内容有所改变&#xff0c;为适应软件版本的变化&#xff0c;特此创作此专栏便于大家学习。本专栏使用软件为&#xff1a;SPSS25.0 本专栏所有的数据文件请点击此链接下…

【Java 进阶篇】JQuery DOM操作:通用属性操作的绝妙魔法

在前端的舞台上&#xff0c;JQuery犹如一位魔法师&#xff0c;为我们展现了操纵HTML元素的奇妙技巧。而在这个技巧的精妙组成中&#xff0c;通用属性操作是一门绝妙的魔法。在本篇博客中&#xff0c;我们将深入研究JQuery DOM操作中的通用属性操作&#xff0c;揭示这段魔法的神…

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

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

海上船舶交通事故VR模拟体验低成本高效率-深圳华锐视点

在海上运输行业&#xff0c;安全事故的防范和应对能力是企业安全教育的重中之重。针对这一问题&#xff0c;海上运输事故VR模拟逃生演练成为了一种创新且高效的教育手段。通过这种演练&#xff0c;企业能够在提升员工安全意识和技能方面获得多方面的帮助。 在VR船舶搜救演练中&…

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

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

Python数据容器(序列操作)

序列 1.什么是序列 序列是指&#xff1a;内容连续、有序。可以使用下标索引的一类数据容器 列表、元组、字符串。均可以视为序列 2.序列的常用操作 - 切片 语法&#xff1a;序列[起始下标:结束下标:步长]起始下标表示从何处开始&#xff0c;可以留空&#xff0c;留空视作从…

11.13 牛客刷题8/10

11.13 信号完整性 指针地址 的加减&#xff0c;注意 最后转为16进制