Java 数据结构篇-实现单链表核心API

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

文章目录

        1.0 单链表的说明

        2.0 单链表的创建

        2.1 单链表 - 头插节点

        2.2 单链表 - 遍历

        2.2.1 使用简单的 for/while 循环

        2.2.2 实现 forEach 方法

        2.2.3 实现迭代器的方法

        2.3 单链表 - 尾插节点

        2.4 单链表 - 通过索引获取数据

        2.5 单链表 - 通过索引插入数据

        2.6 单链表 - 头删节点

        2.7 单链表 - 根据节点来删除数据

         3.0 实现单链表的完整代码

        4.0 实现加 "哨兵" 的单链表 


        1.0 单链表的说明

        单链表是一种数据结构。数据结构是指数据的组织、管理和存储的方式,而单链表是一种常见的线性数据结构,用于存储一系列具有相同类型的元素。它由一系列节点组成每个节点包含一个数据元素和一个指向下一个节点的指针。单链表可以通过指针的方式实现元素的插入、删除和查找等操作。

        2.0 单链表的创建

       把单链表封装成一个类,面向对象编程的思路。类中需要的成员变量为头节点节点,又可以把节点封装成一个类,为更好把节点类封装起来,将其设置为静态内部类

代码如下:

public class SingleLists{//头节点private Node hand = null;//节点private static class Node {//数据private int data;//指向下一个节点private Node next;public Node() {}}

        注意的是,这些成员都不会对外访问的,所以需要把成员变为私有成员

        2.1 单链表 - 头插节点

       顾名思义,就是在头节点处插入新的节点,使其成为新的头节点。需要考虑两种情况,第一种情况,头节点为 null 时,直接就可以将创建出来的对象被 hand 引用了。第二种情况,头节点不为 null 时,需要将创建的对象的 next 引用指向 hand 的引用,而当前创建的对象就要被 hand 引用。

代码如下:

    //实现头插节点public void addFirst(int data) {
/*        if (hand == null){hand = new Node(data,null);}else {hand = new Node(data,hand);}*///对以上代码进行简化得以下代码:hand = new Node(data,hand);}

        需要注意的时,该 API 是对外访问。

        2.2 单链表 - 遍历

        实现遍历的方式有三种:

        第一种方式,使用简单的 for/while 循环。

        第二种方式,实现 forEach 方法。

        第三种方式,实现迭代器的方法。

        2.2.1 使用简单的 for/while 循环

        一般 hand 是不会去移动或者去改变其引用,则需要临时的变量 p 来指向 hand 的对象。循环的条件为 p != null,每一次循环结束都需要 p 去移动到下一个节点处,p = p.next

代码如下:

    //遍历方式:打印方式public void myPrint(){if (hand == null){throw new RuntimeException("hand is null!!!!");}//第一种:
/*        Node p = hand;//用while来实现while (p != null){System.out.println(p.data);p = p.next;}*///第二种://用for来实现for (Node p = hand;p !=null;p = p.next){System.out.println(p.data);}}

        还需要注意,for 与 while 这两种的实现逻辑是一样的,假如 hand 的引用为空,则没必要去循环了,直接去抛出错误。

        2.2.2 实现 forEach 方法

        对于 for/while 的遍历方法直接把 “方法写死了”,forEeach 方法是对 for/while 的方法进行了升级。参数为 Consumer<Integer> 内部类,再重写 accept 方法。

代码如下:

    //遍历方式:实现forEach,由于跟以下的代码有冲突,先改名为 myForEach。public void myForEach(Consumer<Integer> consumer){for (Node p = hand; p != null;p = p.next){consumer.accept(p.data);}}

具体调用该方法的使用:

        singleLists.myForEach(new Consumer<Integer>() {@Overridepublic void accept(Integer integer) {System.out.println(integer);}});

        这样对外获取的数据可以自由支配使用,不仅仅打印输出了。

        2.2.3 实现迭代器的方法

        需要实现接口 Iterable<Integer> ,该接口支持泛型,目前先实现整数类型的单链表。重写 hasNext()next() 方法。

代码如下:

    //遍历方式:使用迭代器循环@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {Node p = hand;@Overridepublic boolean hasNext() {return p != null;}@Overridepublic Integer next() {int k = p.data;p = p.next;return k;}};

        重写完的 hasNext() 这个方法的作用:是判断当前 p 是否为 null ,如果是就停止遍历,结束了。反之继续遍历下去。

        重写之后的 next() 方法的作用:做了两个动作,第一个动作就是获取当前的数据;第二个动作就是将 p 移向下一个节点。

具体调用该方法的使用:

        for (Integer value:singleLists) {System.out.println(value);}

        同理,这个方式不仅仅只有打印输出了,自由支配使用。

        2.3 单链表 - 尾插节点

        找最后的节点后面插入新的节点,如果只有头节点,需要不断的遍历,直到最后一个节点。遍历的条件为 p.next != null,跟以上的条件需要区别开来,这里需要得到最后的节点,可不能 p !=null 一直下去,这样就会找不到最后的节点。

代码如下:

    //尾插节点public void addLast(int data) {if (hand == null) {addFirst(data);return;}Node p = hand;while (p.next != null){p = p.next;}p.next = new Node(data,null);}

        需要注意的是,单独分开当 hand 为 null 时,因为 hand.next == null 了,但是对于hand 为 null 时也可以插入节点呀,所以 当 hand 为 null 时,可以调用头插节点的方法。

        2.4 单链表 - 通过索引获取数据

        单链表是不连续的,不用直接通过索引来访问节点去读取数据,因此,先独立设置一个方法,需设置一个 i 记数点,每一个遍历完 i++ ,直到 i == index 时,先返回该节点。再独立另一个方法,通过该节点来读取该数据。

代码如下:

    //根据索引获取某个节点private Node findNode(int index) {int i = 0;for (Node p = hand ; p != null ; p = p.next,i++){if (i == index) {return p;}}return null;}//根据索引得到对应的数据public int get(int index) {Node p = findNode(index);if (p == null){throw new RuntimeException("找不到该索引!!!");}return p.data;}

        对于找不到的节点,需要抛出异常,需要注意的是,findNode 方法是不对外访问的,需要封装起来。

        2.5 单链表 - 通过索引插入数据

        先获取插入位置的前一个 prev 节点,然后 prev.next 去指向插入的节点的对象,插入节点的 next 去引用原先 prev.next 的对象。

代码如下:

    //根据索引插入数据public void insert(int index , int data){if (index == 0){addFirst(data);}Node prev = findNode(index - 1);if (prev == null){throw new RuntimeException("index is illegal");}prev.next =  new Node(data,prev.next);}

         需要注意的是,先判断插入点是否为头节点,如果是头节点,则调用头插的方法。再去判断其他情况通过 findNode() 方法是否得到 null,如果是,需要抛出异常。

        2.6 单链表 - 头删节点

        顾名思义直接删除头节点,思路为: hand 这个引用需要指向 hand.next ,这就是删除了第一个节点,没用引用的对象,在 Java 中回自动回收的,不会占内存,这也就是删除了。

代码如下:

    //头删节点public void removeFirst() {if (hand == null){throw new RuntimeException("There are no nodes anymore");}hand = hand.next;}

        需要注意,删除前先判断 hand 是否为 null 。

        2.7 单链表 - 根据节点来删除数据

        先找到要删除节点的前一个 prev 节点,然后再去找到 要删除的节点 move = prev.next ,接着将 prev.next = move.next 即可。

代码如下:

    //根据索引来删除节点public void remove(int index) {if (index == 0) {removeFirst();return;}Node prev = findNode(index - 1);if (prev == null){throw new RuntimeException("There are no nodes anymore");}Node move = prev.next;if (move == null) {throw new RuntimeException("There are no nodes anymore");}prev.next = move.next;}

        在删除节点的时候需要先判断 index 是否为 0,如果是,则调用头删的方法,再通过 findNode() 方法来找到删除节点的前一个节点,如果得到的节点为 null,则需要抛出异常,同样的如果得到的需要删除的节点为 null ,则需要抛出异常。

 

         3.0 实现单链表的完整代码


import java.util.Iterator;
import java.util.function.Consumer;//整体
public class SingleLists implements Iterable<Integer>{//头节点private Node hand = null;//节点private static class Node {//数据private int data;//指向下一个节点private Node next;public Node() {}public Node(int data, Node next) {this.data = data;this.next = next;}}//实现头插节点public void addFirst(int data) {
/*        if (hand == null){hand = new Node(data,null);}else {hand = new Node(data,hand);}*///对以上代码进行简化得以下代码:hand = new Node(data,hand);}//遍历方式:打印方式public void myPrint(){if (hand == null){throw new RuntimeException("hand is null!!!!");}//第一种:
/*        Node p = hand;//用while来实现while (p != null){System.out.println(p.data);p = p.next;}*///第二种://用for来实现for (Node p = hand;p !=null;p = p.next){System.out.println(p.data);}}//遍历方式:实现forEach,由于跟以下的代码有冲突,先改名为 myForEach。public void myForEach(Consumer<Integer> consumer){for (Node p = hand; p != null;p = p.next){consumer.accept(p.data);}}//遍历方式:使用迭代器循环@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {Node p = hand;@Overridepublic boolean hasNext() {return p != null;}@Overridepublic Integer next() {int k = p.data;p = p.next;return k;}};}//尾插节点public void addLast(int data) {if (hand == null) {addFirst(data);return;}Node p = hand;while (p.next != null){p = p.next;}p.next = new Node(data,null);}//根据索引获取某个节点private Node findNode(int index) {int i = 0;for (Node p = hand ; p != null ; p = p.next,i++){if (i == index) {return p;}}return null;}//根据索引得到对应的数据public int get(int index) {Node p = findNode(index);if (p == null){throw new RuntimeException("找不到该索引!!!");}return p.data;}//根据索引插入数据public void insert(int index , int data){if (index == 0){addFirst(data);}Node prev = findNode(index - 1);if (prev == null){throw new RuntimeException("index is illegal");}prev.next =  new Node(data,prev.next);}//头删节点public void removeFirst() {if (hand == null){throw new RuntimeException("There are no nodes anymore");}hand = hand.next;}//根据索引来删除节点public void remove(int index) {if (index == 0) {removeFirst();return;}Node prev = findNode(index - 1);if (prev == null){throw new RuntimeException("There are no nodes anymore");}Node move = prev.next;if (move == null) {throw new RuntimeException("There are no nodes anymore");}prev.next = move.next;}}

        4.0 实现加 "哨兵" 的单链表 

        哨兵是单链表中的一个特殊节点,它不在乎存储什么数据元素,只用于标记链表的开始或结束。在单链表中,通常有一个头节点作为链表的起始位置。而哨兵节点是在头节点之前或尾节点之后的一个额外节点,用于简化链表的操作。简单来说,就是 hand 不在为 null ,始终有节点,这么一来,就会节省了考虑 hand == null 的情况发生了,写出来的代码更加简洁了。

加 "哨兵" 的代码如下:

import java.util.Iterator;
import java.util.function.Consumer;//整体
public class SingleLists implements Iterable<Integer>{//头节点private final Node hand = new Node(0,null);//节点private static class Node {//数据private int data;//指向下一个节点private Node next;public Node() {}public Node(int data, Node next) {this.data = data;this.next = next;}}//实现头插节点public void addFirst(int data) {//对以上代码进行简化得以下代码://hand.next = new Node(data,hand.next);insert(0,data);}//遍历方式:打印方式public void myPrint(){for (Node p = hand.next;p !=null;p = p.next){System.out.println(p.data);}}//遍历方式:实现forEach,由于跟以下的代码有冲突,先改名为 myForEach。public void myForEach(Consumer<Integer> consumer){for (Node p = hand.next; p != null;p = p.next){consumer.accept(p.data);}}//遍历方式:使用迭代器循环@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {Node p = hand.next;@Overridepublic boolean hasNext() {return p != null;}@Overridepublic Integer next() {int k = p.data;p = p.next;return k;}};}//尾插节点public void addLast(int data) {Node p = hand;while (p.next != null){p = p.next;}p.next = new Node(data,null);}//根据索引获取某个节点private Node findNode(int index) {int i = -1;for (Node p = hand ; p != null ; p = p.next,i++){if (i == index) {return p;}}return null;}//根据索引得到对应的数据public int get(int index) {Node p = findNode(index);if (p == null){throw new RuntimeException("找不到该索引!!!");}return p.data;}//根据索引插入数据public void insert(int index , int data){Node prev = findNode(index - 1);if (prev == null){throw new RuntimeException("index is illegal");}prev.next =  new Node(data,prev.next);}//头删节点public void removeFirst() {//hand = hand.next;remove(0);}//根据索引来删除节点public void remove(int index) {Node prev = findNode(index - 1);if (prev == null){throw new RuntimeException("There are no nodes anymore");}Node move = prev.next;if (move == null) {throw new RuntimeException("There are no nodes anymore");}prev.next = move.next;}
}

        需要注意的是,哨兵节点并不是必需的,可以根据具体的需求来决定是否使用哨兵节点。在某些情况下,如果链表的操作较为简单,不涉及插入和删除等复杂操作,可以不使用哨兵节点来简化代码。

 

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

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

相关文章

UE5 中的computer shader使用

转载&#xff1a;UE5 中的computer shader使用 - 知乎 (zhihu.com) 目标 通过蓝图输入参数&#xff0c;经过Compture Shader做矩阵运算 流程 1. 新建插件 2. 插件设置 3. 声明和GPU内存对齐的参数结构 4. 声明Compture Shader结构 5. 参数绑定 6. 着色器实现 7. 分配 work gr…

VueRouter

路由介绍 1.思考 单页面应用程序&#xff0c;之所以开发效率高&#xff0c;性能好&#xff0c;用户体验好 最大的原因就是&#xff1a;页面按需更新 比如当点击【发现音乐】和【关注】时&#xff0c;只是更新下面部分内容&#xff0c;对于头部是不更新的 要按需更新&#…

Git 基本使用命令

Git 基本使用命令 下面是一些常用的 Git 基本使用命令&#xff1a; 初始化一个新的 Git 仓库&#xff1a; git init克隆&#xff08;Clone&#xff09;一个远程仓库到本地&#xff1a; git clone <repository_url>添加文件或目录到暂存区&#xff08;Staging Area&am…

微信小程序前端环境搭建

搭建微信小程序前端环境 申请小程序测试账号 访问路径 使用微信扫描二维码进行申请&#xff0c;申请成功之后&#xff0c;进入界面&#xff0c;获取小程序ID(AppID)和秘钥(AppSecret) 安装微信web开发者工具 访问路径 选择稳定开发的版本 需要在小程序的设置中将默认关闭…

geoserver发布tif矢量数据图层

cesium加载上传至geoserver的tif矢量数据_cesium加载tiff-CSDN博客 geoserver安装及跨域问题解决方案&#xff1a;geoserver安装及跨域问题解决方案_geoserver 跨域_1 1王的博客-CSDN博客 将TIF上传至geoserver 启动geoserver服务&#xff0c;并进入geoserver主页。 1. 新建…

【物联网产品架构】如何构建物联网产品路线图

面对现实吧。建立物联网产品路线图难度要比为“正常”技术产品制定路线图要困难得多。 这是因为IoT产品是复杂的系统。为了创建一个工作的解决方案&#xff0c;物联网技术栈的所有层 - 设备硬件&#xff0c;设备软件&#xff0c;通信&#xff0c;云平台和云应用都需要一起工作。…

Spring Cloud五大组件

Spring Cloud五大组件 Spring Cloud是分布式微服务架构的一站式解决方案&#xff0c;在Spring Boot基础上能够轻松搭建微服务系统的架构。 现有Spring Cloud有两代实现&#xff1a; 一代&#xff1a;Spring Cloud Netflix&#xff0c;主要由&#xff1a;Eureka、Ribbon、Feig…

【c语言】 逻辑运算符运算规则

1.&&逻辑运算符的坑 int x0&#xff0c;y0&#xff0c;z0; z (x1) && (y2); printf("%d"&#xff0c;y);//y0;今天遇到了同学问的问题&#xff0c;为什么y输出为0. 我第一时间也记不得&#xff0c;工作中一般不会写这种代码&#xff0c;但是却不能…

Vue3 状态管理 - Pinia

1. 什么是Pinia Pinia 是 Vue 的专属的最新状态管理库 &#xff0c;是 Vuex 状态管理工具的替代品 提供更加简单的APl&#xff08;去掉了mutation&#xff0c;Pinia 中对state数据的修改可以直接通过action&#xff0c;Vuex中则是通过mutation)提供符合组合式风格的API&#…

笔记转移:https://www.yuque.com/u32968635/lbk

语雀&#xff1a;https://www.yuque.com/u32968635/lbk

视频剪辑技巧:如何高效批量转码MP4视频为MOV格式

在视频剪辑的过程中&#xff0c;经常会遇到将MP4视频转码为MOV格式的情况。这不仅可以更好地编辑视频&#xff0c;还可以提升视频的播放质量和兼容性。对于大量视频文件的转码操作&#xff0c;如何高效地完成批量转码呢&#xff1f;现在一起来看看云炫AI智剪如何智能转码&#…

Servlte+JSP企业内容管理系统

企业内容管理系统的设计与实现 1&#xff0e;系统概述: 随着企事业单位信息化的建设&#xff0c;内联网和外联网之间的信息交互越来越多,优秀的内容管理系统对企业内部来说&#xff0c;能够很好地做到信息的收集和重复利用及信息的增值利用。对于外联网来说,内容管理系统可使…

6 Go的切片

概述 在上一节的内容中&#xff0c;我们介绍了Go的数组&#xff0c;包括&#xff1a;声明数组、初始化数组、访问数组元素等。在本节中&#xff0c;我们将介绍Go的切片。在Go语言中&#xff0c;数组的长度是固定的&#xff0c;不能改变&#xff0c;这在某些场景下使用不太方便。…

【C++】一文简练总结【多态】及其底层原理&具体应用(21)

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 目录 一.多态的概念二.多态的实现1&#xff…

【C++】:拷贝构造函数与赋值运算符重载的实例应用之日期类的实现

C实现日期类 ├─属性&#xff1a; │ ├─年份 │ ├─月份 │ └─日期 ├─方法&#xff1a; │ ├─构造函数 │ ├─拷贝构造函数 │ ├─析构函数 │ ├─设置年份 │ ├─设置月份 │ ├─设置日期 │ ├─获取年份 │ ├─获取月份 │ ├─获取日期 │ ├…

websocket和mqtt

WebSocket是一种通信协议&#xff0c;它允许在浏览器和服务器之间建立持久连接&#xff0c;并允许双向传递数据。MQTT则是一种轻量级的发布/订阅消息传输协议&#xff0c;常用于物联网(IoT)设备之间的通信。 &#xff08;1&#xff09;js能直接实现mqtt吗&#xff0c;还是需…

已解决java.lang.IllegalStateException: Duplicate key

已解决java.lang.IllegalStateException: Duplicate key 文章目录 报错问题解决思路解决方法交流 报错问题 java.lang.IllegalStateException: Duplicate key 解决思路 java.lang.IllegalStateException: Duplicate key 是由于在使用 Map 或 Set 时&#xff0c;试图将一个已经…

十、sdl显示yuv图片

前言 SDL中内置加载BMP的API&#xff0c;使用起来会更加简单&#xff0c;便于初学者学习使用SDL 如果需要加载JPG、PNG等其他格式的图片&#xff0c;可以使用第三方库&#xff1a;SDL_image 测试环境&#xff1a; ffmpeg的4.3.2自行编译版本windows环境qt5.12sdl2.0.22&…

redis的性能管理和雪崩

redis的性能管理 redis的数据是缓存在内存当中的 系统巡检&#xff1a; 硬件巡检、数据库、nginx、redis、docker、k8s 运维人员必须要关注的redis指标 在日常巡检中需要经常查看这些指标使用情况 info memory #查看redis使用内存的指标 used_memory:11285512 #数据占用的…

最简单的简历练习

代码&#xff1a; <!DOCTYPE html> <html> <head> <title>我的简历</title> <style> body { background-image: url(https://picsum.photos/id/1018/1000/1000); background-size: cover; …