Java自定义实现单链表

2023-05-16

目录

一、自定义java单链表原理概述

二、自定义java单链表功能实现细节

三、实现代码


一、自定义java单链表原理概述

        1、单链表概念

                单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点(node)来表示的,每个结点的构成是由元素 +指针(指示后继元素的存储位置),元素就是存储数据的存储单元,指针就是指向下一个节点的地址。

        2、单链表的特点及复杂度分析

                单链表无法像数组一样能够随机访问某个节点,只能从表头向下逐个遍历至目标节点(复杂度为O(n));单链表的增删操作都仅需要修改节点的next来达到目的,所以时间复杂度均为O(1),效率高,但是链表的元素的添加存在new操作,当操作规模庞大时,单链表的性能是会受到影响的,对于内存的消耗较大,效率反而会降低(可对比于上一篇——自定义动态数组)。

        3、Node结构示意图

        4、Node示意图解析

                如上图所示,我们定义一个Node类,在该类中定义两个成员变量分别为value和next,其中value为该节点的值,泛型化以满足用户更丰富的需求;next为下一个节点(引用/指针),到此,有些人会产生疑问,为什么传统概念上说next是下一节点的指针,这里为什么还要用Node类型呢?指针又将放在哪呢?

                对此,是因为我们忽略了一个最普遍的概念, 那就是对于java的单链表来说,它是区别于C/C++语言的,一般来说java中的对象存在于内存中(有可能存在于栈中——逃逸分析),对应着内存中的唯一一个地址,也就是指针。所以next就相当于是递归调用了Node的构造函数,存放了该节点的下一个节点,简单来说:next对象就相当于是下一个节点的引用。

        5、单链表的应用

                常见应用于InnoDB存储引擎中的pageB+树叶子节点中,叶子节点中的数据为单链表(节点之间为双向链表)、LRU cache。

二、自定义java单链表功能实现细节

        1、虚拟头结点VirtualNode

                 如上图,在自定义单链表LinkedList类中,设置了两个成员变量,size表示链表的长度;virtualHead为链表的虚拟头结点;

                虚拟头结点的作用是为链表预设一个逻辑上的头结点,实质该节点是并不存在的,此时链表的size还是为0的,这仅仅是为了使链表的实现逻辑通顺易懂。在设置虚拟头结点后,此时链表中的每一个节点都有一个前趋节点;将虚拟节点设置为链表的第一个节点,当链表为空时,在此基础上,再进行添加新节点的操作时,便可直接将该虚拟节点的next设为要添加的新节点,即对virtualHead节点的next进行赋值操作与size++即可;同理在删除、遍历,获取对应位置节点值等操作中,头结点便同其他节点一样,不再具有特殊性,没有虚拟头结点的特殊之处是因为在没有虚拟头结点时对于单链表元素的操作都是通过对目标节点的前一个节点的next进行管理,而头结点并没有前一个节点,这时就需要进行特殊处理。

        2、对于LinkedList的封装,是将Node类作为LinkedList的一个内部类,并不独立出来为一个Node类,这是因为对于使用者来说,Node类的意义不大,用户只需要通过LinkedList类创建一个单链表,然后进行RUD等操作即可,这也是对类封装性的加强。

        3、功能概览

三、实现代码

package custom.linked;

/**
 * @author ghCode
 * @Email:2085264964@qq.com 自定义实现单向链表
 */
public class LinkedList<E> {
    /**
     * Node节点内部类
     * value 节点值
     * next 下一节点(java中对象每个对象都指向一个地址,所以递归使用Node类型)
     */
    private class Node {
        public E value;
        public Node next;

        public Node() {
            this.value = null;
            this.next = null;
        }

        public Node(E value) {
            this.value = value;
            this.next = null;
        }

        public Node(E value, Node next) {
            this.value = value;
            this.next = next;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "value=" + value +
                    '}';
        }
    }

    private Node virtualHead;//虚拟头结点
    private int size;//链表长度

    /**
     * 无参构造,为LinkedList设置一个虚拟头结点
     */
    public LinkedList() {
        virtualHead = new Node();
        size = 0;
    }

    /**
     * 获取链表长度
     *
     * @return size
     */
    public int getSize() {
        return size;
    }

    /**
     * 判断链表是否为空
     *
     * @return true-空 false-非空
     */
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 在指定的两个节点之间添加新节点(不支持在头尾添加)
     *
     * @param previousIndex 前趋节点
     * @param afterIndex    后继节点
     * @param element       新节点的值
     * @return true-添加成功 false-添加失败
     */
    public boolean addBetweenIndex(int previousIndex, int afterIndex, E element) {
        if (previousIndex < 0 || afterIndex < 0) {
            throw new IllegalArgumentException("index out of bounds,the arg is illegal!");
        }
        if (previousIndex > size || previousIndex > size - 1) {
            throw new IllegalArgumentException("index out of bounds,the arg is illegal!");
        }
        if (size < 2) {
            throw new RuntimeException("method is not suit your demand!");
        }
        Node previousNode = virtualHead;//从头结点开始,遍历搜索至preciousIndex处的节点
        for (int i = 0; i < afterIndex; i++) {
            previousNode = previousNode.next;
        }
        /*
        将当前(未添加新节点)previous节点的下一个节点作为新节点的next节点构造出要添加的新节点,
        并将该新节点设为当前(未添加新节点)previous节点的next节点。
         */
        previousNode.next = new Node(element, previousNode.next);
        size++;
        return true;
    }

    /**
     * 向头结点前添加一个新节点
     *
     * @param element 新节点的值
     * @return true-添加成功
     */
    public boolean addFirst(E element) {
        Node previousNode = virtualHead;
        previousNode.next = new Node(element, previousNode.next);
        size++;
        return true;
    }

    /**
     * 向尾结点后添加一个新节点
     *
     * @param element 新节点的值
     * @return true-添加成功
     */
    public boolean addLast(E element) {
        Node previousNode = virtualHead;
        if (size == 0) {
            previousNode = virtualHead;
            previousNode.next = new Node(element, null);
            size++;
            return true;
        }
        for (int i = 0; i < size; i++) {
            previousNode = previousNode.next;
        }
        previousNode.next = new Node(element, null);
        size++;
        return true;
    }

    /**
     * 删除指定位置的节点
     *
     * @param index 节点索引
     * @return 被删除的节点值
     */
    public E deleteIndexNode(int index) {
        if (index < 0 || index > size - 1) {
            throw new IllegalArgumentException("the index is illegal!");
        }
        if (index == 0) {
            deleteHeadNode();
        }
        if (index == size - 1) {
            deleteLastNode();
        }
        Node previousNode = virtualHead;
        for (int i = 0; i < index; i++) {
            previousNode = previousNode.next;
        }
        E deleteElement = previousNode.next.value;
        previousNode.next = previousNode.next.next;
        size--;
        return deleteElement;
    }

    /**
     * 删除头结点
     *
     * @return 被删除的头结点值
     */
    public E deleteHeadNode() {
        Node previousNode = virtualHead;
        E deleteElement = previousNode.next.value;
        previousNode.next = previousNode.next.next;
        size--;
        return deleteElement;
    }

    /**
     * 删除尾结点
     *
     * @return 被删除的尾结点的值
     */
    public E deleteLastNode() {
        Node previousNode = virtualHead;
        for (int i = 0; i < size - 1; i++) {
            previousNode = previousNode.next;
        }
        E deleteElement = previousNode.next.value;
        previousNode.next = null;
        size--;
        return deleteElement;
    }

    /**
     * 获取对应位置的节点的值
     *
     * @param index 节点索引
     * @return 节点值
     */
    public E getIndexElement(int index) {
        Node previousNode = virtualHead;
        for (int i = 0; i < index; i++) {
            previousNode = previousNode.next;
        }
        return previousNode.next.value;
    }

    /**
     * 遍历链表
     */
    public void traverseList() {
        Node previousNode = virtualHead;
        for (int i = 0; i < size; i++) {
            previousNode = previousNode.next;
            System.out.println(previousNode.value);
        }
    }

    /**
     * 反转链表
     *
     * @return 反转后的新链表
     */
    public LinkedList<E> reversed() {
        LinkedList<E> newList = new LinkedList();//存放反转后的链表
        for (int i = 0; i < size; i++) {
            newList.addFirst(getIndexElement(i));
        }
        return newList;
    }

    /**
     * 单链表转数组
     *
     * @return 转换后的数组
     */
    public E[] toArray() {
        E[] array = (E[]) new Object[size];
        for (int i = 0; i < size; i++) {
            array[i] = getIndexElement(i);
        }
        return array;
    }

    /**
     * 空单链表通过数组构造新链表(this)
     *
     * @param array 目标数组
     * @return 构造后的链表(this)
     */
    public LinkedList constructorLinkedList(E[] array) {
        if (!isEmpty()) {
            throw new RuntimeException("LinkedList is must be empty!");
        }
        for (int i = 0; i < array.length; i++) {
            addLast(array[i]);
        }
        return this;
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java自定义实现单链表 的相关文章

  • 将 Uri 转换为字符串以及将字符串转换为 Uri

    我正在开发一些应用程序 它允许从 SD 卡中选择图像 将其保存到数据库中并为 ImageView 设置该值 我需要知道将 uri 转换为字符串以及将字符串转换为 uri 的方法 现在我使用了 Uri 的 getEncodedPath 方法
  • Java NIO Pipe 与 BlockingQueue

    我刚刚发现它只有一个 NIO 工具 即 Java NIO Pipe 它是为在线程之间传递数据而设计的 与通过队列 例如 ArrayBlockingQueue 传递的更传统的消息相比 使用此机制是否有任何优势 通常 将数据传递给另一个线程进行
  • 涉及数学的方法给出与计算器不同的答案

    我是java新手 所以请耐心等待 我试图从比赛总数中获得胜利的百分比 但我正在做的事情还很遥远 我获取百分比的方法如下 public double winPercentage int wins int total return wins t
  • Java 中具有级别顺序插入的完整二叉搜索树

    我们接到一个任务 需要编码 二叉搜索树 那个树has to be complete not perfect 这意味着所有不在最低级别或次低级别的节点都应该有 2 个子节点 而最低级别的节点应尽可能远离左侧 我们需要插入到树中等级顺序 所以如
  • 我在 Android Studio 中使用哪个版本的 JDK 有关系吗?

    I know I can choose the SDK location in Android Studio s Project Structure 我有两个问题 当我们已经使用Android SDK时 为什么还需要JDK 毕竟我们不是为
  • 如何设置Java线程的CPU核心亲和力?

    我搜索了以前关于类似主题的帖子 但找不到合适的答案 因此提出这个问题 非常感谢您帮助回答 我知道在 Linux 中通过任务集命令设置进程与特定 CPU 核心的关联性 但我想设置 Java 线程与特定 cpu 核心的亲和力 以便属于同一进程的
  • Java setLocation() 事故

    我正处于创建一个程序来操作员工 客户系统的开始阶段 现在我刚刚创建了登录 GUI 但我遇到了一些问题 setLocation 方法 我将其设置为 250 250 但这使我的 GUI 高度变得非常疯狂 如果有人能够解决这个问题 我的代码如下
  • Bean 属性不可读或具有无效的 getter 方法

    因此 我的任务是为注册表路由编写一个简单的 Web 应用程序 使用 Spring MVC 所以我有 路线 类 我想在其中保留起点 终点和中间点列表 但我不明白如何将值从 jsp 放入列表 例如使用 jstl 所以我决定解析一个字符串 pub
  • 如何从网上获取源代码?

    我正在尝试从 Web 获取 HTML 源代码 我尝试这样做 u new URL url URLConnection con u openConnection con setRequestProperty User Agent Mozilla
  • Java SSO 与 Wildfly 8、Java 1.8.0_45 和 Active Directory

    我对这个主题进行了很多搜索 但找不到解决方案 要求的简短描述 Wildfly 8 2 下 Web 应用程序上的 SSO 在 Active Directory 中验证 Windows 用户的身份 当 SSO 失败时回退到登录表单 在 Wild
  • Jlist 自定义渲染器

    我正在尝试添加一个我猜你会称其为列表中每个项目的子列表 我构建了一个自定义渲染器 它提供以下输出 正如你所看到的 有些东西不对劲 我没能找到问题的答案 我猜我需要更改面板布局中的某些内容才能获得正确的结果 但不知道是什么 https i s
  • 错误:找不到符号 ArrayList

    我正在尝试创建某种列表来存储数组 表 中的值 我在这里使用数组列表 但我应该使用列表吗 但是 每次我尝试编译时 它都会引发以下错误 找不到标志 符号 ArrayList类 位置 玩家类 TablePlayer 代码如下 public cla
  • Eclipse java 断点 - 目的是什么?

    我正在学习 Android 教程 刚刚进入调试部分 我想知道断点的用途是什么 我还不能告诉 它实际上停止了应用程序 以便我可以确定它运行到该点 或者我可以设置多个断点并将它们用作标记来从断点到断点检查 停止和运行 我的代码 断点是执行停止的
  • 从 Apache Kafka 中的主题删除消息

    所以我是 Apache Kafka 的新手 我正在尝试创建一个简单的应用程序 以便我可以更好地理解 API 我知道这个问题在这里被问了很多 但是如何清除存储在主题上的消息 记录 我看到的大多数答案都说要更改消息保留时间或删除并重新创建主题
  • 我可以使用本机系统窗口作为父窗口使 JDialog 成为模式吗?

    我有一个 JDialog 窗口 我需要使其成为模态窗口 但父窗口不是 Java 窗口 而是本机 Windows 操作系统窗口 是否可以 不 你不能 您甚至无法不仅引用本机窗口 甚至无法引用运行在其他 JVM 中的 java 应用程序创建的窗
  • 使用 Swift 在 iOS 和 Android 之间共享核心代码

    我想要的是 使用 Swift 在 Android 和 iOS 之间共享非 UI 代码 问题 Android 具有 NDK 支持 允许您使用 Java 本机接口 JNI 运行 C 和 C 代码 不是 Objective C 我是一名Java程
  • Apache Beam:如何在使用重复数据删除功能时解决“ParDo 需要确定性密钥编码器才能使用状态和计时器”

    我正在尝试使用 Apache Beam 的重复数据删除功能对来自 Google Cloud Pubsub 的输入消息进行重复数据删除 但是 我创建后遇到错误KV
  • Android 调整图片大小

    我的图像存储在 SD 卡上 每个大小约为 4MB 我想调整每个的大小 而不是将其设置为 ImageView 但我不能使用BitmapFactory decodeFile path 因为异常 java lang OutOfMemoryErro
  • 如何从项目文件夹中的 jlabel 上设置图像?

    我正在尝试制作一个 Java 桌面应用程序 我想设置一个图像JLabel 我正在使用 NetBeans 从我的项目文件夹中 我的目录结构是 F gt MARKET src lib src defaultpackage demo java i
  • Java中不同格式的字符串解析为日期

    我想转换String to Date以不同的格式 例如 我从用户那里得到 String fromDate 19 05 2009 i e dd MM yyyy format 我想转换这个fromDate作为日期对象 yyyy MM dd fo

随机推荐

  • 前端知识点(三):Flex布局(弹性布局)

    1 定义flex布局 1 display flex 2 行内元素 display inline flex 父元素中设置为flex布局 xff0c 形成flex容器 子元素的float clear vertical align属性将失效 2
  • 网络服务---OSI七层参考模型及各层工作原理详解

    OSI网络模型概念 OSI模型 xff08 Open System Interconnection Reference Model xff09 是指国际标准化组织 ISO 提出的一个试图使各种计算机在世界范围内互连为网络的标准框架 xff0
  • 树莓派官方系统连接电脑(电视)显示器无信号输出的解决方法

    一 打开盘符中的config文件 二 对第五行进行修改 修改前 xff1a 将 hdmi safe 61 1 修改为 xff1a hdmi safe 61 1 overscan left 61 30 overscan right 61 30
  • Cannot read properties of null (reading ‘style‘)前端错误记录21/10/20

    前端错误记录 Vue报错Cannot read properties of null reading style Vue报错 Cannot read properties of null reading style 起因 xff1a Vue
  • Java获取国内各个地区实时天气

    获取国内各个地区实时天气 不废话直接上代码 span class token keyword public span span class token keyword static span span class token class n
  • MyBatis-Plus——条件构造器Wapper、QUeryWrapper、UpdateWrapper、LambdaQueryWrapper、LambdaUpdateWrapper(详解)

    目录 一 条件构造器简介 二 QueryWrapper组装查询条件 三 QueryWrapper组装排序条件 四 QueryWrapper组装删除条件 五 QueryWrapper实现修改功能 六 QueryWrapper条件的优先级 七
  • 左移和右移的算法

    lt lt 左移 1 运算规则 xff1a 按二进制形式把所有的数字向左移动对应的位数 xff0c 高位移出 舍弃 xff0c 低位的 空位补零 2 语法格式 xff1a 需要移位的数字 lt lt 移位的次数 例如 xff1a 3 lt
  • 1.Stm32F407系列之点亮LED灯

    点灯大师已上线 xff01 我们在测试一个开发板的好坏 xff0c 或者是验证一些小功能模块是否起作用的时候 xff0c 最简单的方法就是打印输出或者是用LED灯指示 想要控制LED灯的亮和灭 xff0c 只要我们配置好IO口 xff0c
  • 智能机器人软件工程师学习路线

    0 引言 很多朋友对机器人软件开发和人工智能感兴趣 xff0c 不知道怎么学习 xff0c 传智播客武汉校区在今年3月份开设了一期智能机器人软件开发工程师就业班 xff0c 在这里我把就业班的学习曲线给大家介绍一下 0基础小白也能学会的人工
  • 2021-09-29 java命名规范、常量、变量简记

    文章目录 前言一 标识符 关键字 命名规则二 常量 变量 1 常量简介2 变量简介总结 前言 在刚入门java时 xff0c 应该养成代码规范书写的好习惯 xff0c 不应该随意命名 xff0c 符合统一的命名规则 xff0c 也会使别人在
  • Spring 学习笔记(十)渲染 Web 视图 (Apache Tilesa 和 Thymeleaf)

    使用Apache Tiles视图定义布局 为了在Spring中使用Tiles xff0c 需要配置几个bean 我们需要一个TilesConfigurer bean xff0c 它会负责定位和加载Tile定义并协调生成Tiles 除此之外
  • 2021-10-13 关于参数校验及@Valid和@RequestBody注解的组合使用

    一 前言 xff1a 学会并熟悉注解的使用 xff0c 在开发过程中 xff0c 是可以提高效率和简化工作复杂程度的 xff0c 也是会逐渐称为主要编码方式之一 二 1 64 RequestBody注解 xff1a 该注解在处理控制层的请求
  • vue之VueCli4的安装及使用

    一 前言 Vue CLI 是一个基于 Vue js 进行快速开发的完整系统 xff0c 提供 xff1a 通过 64 vue cli 实现的交互式的项目脚手架 通过 64 vue cli 43 64 vue cli service glob
  • 浅谈Mysql数据库

    一 为什么要使用数据库 xff1f 使用一个东西 xff0c 就要清楚它的功能价值 xff0c 才能更好的利用它 xff0c 使我们在工作生活中游刃有余 关于数据库的使用 xff0c 好多人会说 xff0c 一个数据库就是好多张表 xff0
  • java自定义一个数组类(封装多种方法)

    一 自定义数组类的动机 java给定的数组为静态的 xff0c 我们是无法对齐进行灵活的操作 xff0c 比如指定位置添加元素 xff0c 删除元素 xff0c 判断是否非空等 xff0c 于是我们便需要利用 面向对象 的设计模式 xff0
  • 关于JVM(基本常识)

    目录 一 JVM是什么 1 概述 二 为什么要用JVM 1 java程序的执行流程 2 JVM的架构 一 JVM是什么 1 概述 关于JVM xff0c 在百度上的解释为 xff1a JVM是Java Virtual Machine xff
  • JVM之几种常见的JIT优化

    目录 一 公共子表达式消除 xff08 经典的JIT优化技术 xff09 二 方法内联 三 逃逸分析 四 三种逃逸分析优化方式 1 对象的栈上内存分配 2 标量替换 3 同步锁消除 一 公共子表达式消除 xff08 经典的JIT优化技术 x
  • 示例数据库Sakila-db安装(Linux版)

    目录 一 关于Sakila示例数据库 二 安装步骤 三 主要相关命令 一 关于Sakila示例数据库 sakila数据库是国内外对于MySQL研究时广泛使用的一个示例数据库 xff0c 包含了较为大量的数据和使用了合理的数据库结构设计 xf
  • 关于Mysql版本升级迁移数据库时报Error Code: 3554 - Access to system table ‘mysql.innodb_index_stats‘ is rejected错误

    目录 一 背景 二 经过 三 解决 四 总结 一 背景 今天在学习Redis时 xff0c 想到这么一个应用场景 xff1a 如果我们将经常查询的数据先存到Redis中 xff0c 然后每当我们要从数据库查询数据时 xff0c 先查询Red
  • Java自定义实现单链表

    目录 一 自定义java单链表原理概述 二 自定义java单链表功能实现细节 三 实现代码 一 自定义java单链表原理概述 1 单链表概念 单链表是一种链式存取的数据结构 xff0c 用一组地址任意的存储单元存放线性表中的数据元素 链表中