单向链表结构

 链表结构简介

        链表结构是一种用比较特殊的数据结构类型,它也是线性数据结构中的一种,但是与栈结构等线性数据结构不同,它的内部结构并不是一个简单的存储空间,而是一个带有指向性质的单元。要理解链表结构要弄清楚两个问题,第一个就是链表结构的存储方式是怎样的,第二个就是链表结构的模型该如何描述。

        先说第一个问题,链表结构的存储方式。链表结构对数据存储的时候并不是简单的将这个数据存放到一个指定的空间中,而是将数据存储到一个属于链表结构的节点当中。这个节点就是链表的一个组成部分,在这个节点中,不仅存储了这个元素还存储了下一个元素或者上一个元素的地址,通过这些储存的地址就可以找到当前元素的上一个元素或者下一个元素。

        了解完链表结构的存储方式,那么链表结构的模型就比较好建立了。最简单的模型就是链子,不论是铁链还是狗链都能帮助我们理解链表结构。因为链表结构就相当于是用一根无形的绳子将内存中的数据串了起来,就像是一根链子一样,所以称之为链表结构。但是要注意的是,虽然我们可以将链表结构想象成一根铁链,但是在实际内存中,链表结构中相邻的两个元素在内存中并不是相邻的。只是通过上一个节点中存储的地址能找到下一个节点中存储的元素而已。

        综上,链表结构是由许多节点组成的,每一个节点中都应包含数据部分和地址部分两个部分。其中数据部分用来存储该节点的实际数据,而地址部分用来存储上一个或者下一个节点的地址。

        链表结构可以分为单向链表、双向链表和双向循环链表三种,其中单向链表和双向链表是比较常见的链表结构,双向循环链表不太常见,简单了解即可。

单向链表

        单向链表是链表结构中最为简单的一类,它的特点是链表中节点的链接方向是单向的,也就是说访问链表中的元素时只能从头开始一个一个的寻找,不能直接锁定指定元素的位置,也不能从尾向前访问元素。故能够判断出单向链表的结构中节点存储的内容应该为节点存储的元素以及下一个节点的地址。

        了解了单向链表的结构以及存储模式,就可以创建单向链表的具体对象了。目前,在java中是找不到一个合适的结构来描述链表结构的,因此需要自行创建链表实现类。考虑到单向链表和双向链表的除了在存储结构上纯在些许差异之外其余并无太多不同,并且它们都应该具有对内部储存元素进行操作的方法,因此为了提高代码的复用性,可以将链表结构中的方法抽象出来创建一个接口,这样在实现单向链表或者双向链表类的时候只需要实现接口中的方法就能够实现对应的操作方法了。大大降低了代码的复杂程度。根据链表结构中应该具备的操作元素的方法,创建了以下接口:

package com.data.demo;/*** 基于链表结构存取元素的API定义* @param <E>*/public interface MyList <E>{void add (E element);E get (int index);int size();E remove(int index);
}

此接口中定义了四个抽象方法,对应了添加元素、获取元素、获取元素个数以及删除元素四个功能。

        创建完链表结构的接口类,就可以创建一个单向链表类来实现这个接口类了。由于向单向链表中添加元素之前并不能确定添加的元素类型,因此定义的单向链表类应该是一个泛型类,它当中的需要参数的方法应该为泛型方法,这也说名了上面定义的接口为泛型接口的原因。

        在单向链表结构中应该具备这样一个部分,这个部分是用来描述节点内容的。而且很容易发现,节点功能只会在单向链表类中被使用,在其它类中不会被使用。这个描述符合内部类的特征,因此可以在单向链表类中定义一个内部类来存储节点信息。将这个内部类定义为内部泛型类,它的名称为Node,在Node类型中item变量用来存储当前节点的元素,定义一个变量next用来指向下一个节点对象的地址,并且还要提供一个全参构造方法。具体实现如演示代码所示。

        以上定义了单向链表中的用于描述节点的结构,接下来就需要对单向链表中的具体方法做出实现了。

实现添加元素的方法

        结合上文所说,单向链表访问元素必须从头节点开始,不能从尾节点或者链表当中的任意节点开始,因此在实现单向链表中的具体方法之前需要确定单向链表头节点的位置。因此最简单的方法就是定义一个私有的Node类型的成员变量来存储单向链表中头节点的位置。这里将这个变量定义为 head。此外,操作元素的方法中还涉及了元素个数的变动,也应该添加一个私有变量来记录单向链表中的元素个数变动,此处为size。

        随后实现单行链表中添加元素的方法add。这个方法的作用是将指定元素添加到单向链表结构中,根据对单向链表结构的描述,这个方法可以分为三步实现。第一步,创建节点,将传入方法的元素添加到创建的节点当中;第二步,找到尾节点;第三步,实现节点挂接。

        这三步中比较难理解的是第二步和第三步。第二步找到尾节点的原因是添加元素是将元素添加到单向链表的尾节点之后,因此需要先找到尾节点。而在单向链表中,访问元素是从头节点开始的,所以要找到尾节点就得从头节点开始不断向下访问节点,直到访问的节点不再指向下一个元素为止,就找到了尾节点。也就是说,当next的地址为null时,尾节点就找到了。根据描述,第二步应该用一个死循环来实现,具体实现如演示代码中的getTail方法所示。这里要注意,循环结构中的this.node = head;的意思是将head的值赋给一个新的变量node,这样做的目的是因为真正头节点的位置是不能改变的。其次循环实现是通过移动node指向的地址来实现的。如果不满足要求,就像node.next也就是下一个节点的地址传给node,直到node的地址为空。

        第三步实现节点的挂接就稍微好理解点了,因为是在尾节点添加元素,所以新添加的元素成为了尾节点,原来的尾节点不再是尾节点,所以原来节点中指向下一个节点的地址不应为空,而应是新添加的节点。这就是节点挂接的简单描述,具体实现为

               if(tail == null){
                    this.head = node;
                  }else{
                    tail.next = node;

                }

其中tail == null代表没有尾节点,即整个单向链表均为空,这时添加的节点就是头节点,即只需要将添加的节点赋给头节点即可。在拥有尾节点的情况下用代码tail.next = node;即可将尾节点指向的下一个节点指向添加的节点,节点挂接的操作就完成了。当然,添加有元素会使得单向链表中的元素个数发生变动,因此还需要对元素变动进行记录。至此,添加元素的方法就完成了。

实现获取元素的方法

        获取元素的方法是getNode,这个方法需要传入一个int类型的参数作为元素索引,通过索引来获取元素。这里可能会有人提出一个疑问,链表结构存在索引吗?为什么能够通过索引来过去元素?我们先暂时将这个问题搁浅,实现获取元素的方法之后自然就清楚了。

        由于getNode方法是通过索引来获取指定位置的元素的,所以在获取元素之前要先检查给定的索引是否合法。因为编写的单向链表类中的元素是从0这个索引开始的,所以索引的范围为[0,size),即最小为0,最大为元素个数减1。通过单向链表实现的接口能发现,要实现的四个方法中不止getNaoe方法用到了元素索引,所以对索引合法性的判断最好编写成一个独立的方法。在演示代码中体现为checkIndex方法。在这个方法中,如果索引合法则会执行获取索引所指向的元素,如果不合法则会抛出索引异常的的提示。

        当给定的索引合法之后就可以获取索引指向的元素了,那么其实可以发现,单向链表并没有索引,那么这个索引哪儿来的呢?其实这里要联系到添加元素到单向链表的操作,可以发现,添加元素时提供的方法时从尾节点添加的,也就是说这个单向链表中的元素有一个默认顺序,这个顺序就是添加元素的顺序。那么,如果把元素添加的元素作为单向链表中元素的“虚假的索引”就可以通过这个“虚假的索引”来获取单向链表中的元素了。这个想法在getNode这个方法得到了充分的体现。

                                private Node getNode(int index){
                                        Node node = this.head;
                                        for(int i = 0;i < index;i++){
                                            node = node.next;
                                        }
                                        return node;
                                      }

在方法getNode方法的代码中,由于单向链表只能从头节点开始访问元素的限制,所以先将头节点赋给一个新的变量node,随后通过循环结构来访问单向链表中的元素。根据上面描述的原理,获取索引为index的元素等价于获取第index个添加到单向链表中的元素,所以只要将索引index作为循环是否终止的判断条件即可获取到指定索引的元素。如此,先前搁浅的问题便得到了解决,同时获取指定位置的元素的方法也得到了实现。

删除指定索引位置的元素

        删除元素的方法为remove,这个方法仍然需要传入一个索引,此外它还会返回删除的元素的值。由于这个方法有一个索引,所以需要调用方法checkIndex来检验传入索引的合法性。此外也要获取到当前索引指向的节点,以方便后续操作以及返回当前节点中的元素。

        接下来阐述单向链表中删除元素的索引。通过上面的描述知道,单向链表中的元素是通过节点中存储的地址值来链接在一起的,那么如果能够将某个元素上的这个中链接关系删除,就能将这个元素从这个单向链表中移除。所以这里涉及到三个操作,移除上一个节点指向当前节点的地址值,将上一个节点中存储的地址值指向当前节点的下一个节点以及将当前节点存储的地址值置空。以上的这三个操作就是在单项链表中删除指定元素的具体操作,但是在具体操作时又可以分为删除的元素是否为头节点的情况,如果删除的元素时头节点,则只需要将下一个元素赋给头节点,随后将要删除的节点存储的地址置空即可。而当要删除的元素不是头节点时,上面的三个操作就缺一不可了。以上的描述体现在代码中具体为:

                        if(this.head == node){
                            this.head = node.next;
                        }else{
                            Node<E> temp = this.head;
                            for (int i = 0; i < index-1; i++) {
                                temp = temp.next;
                              }
                            temp.next = node.next;
                        }
                        node.next = null;

在这个代码中要注意的是获取前一个节点只需要索引减一即可。然后就是注意中间变量temp的合理运用。当然由于删除了元素,所以记录元素个数的变量也要发生相应的变化。

获取元素个数的方法

        获取元素个数的方法极为简单,因为在添加元素和删除元素的操作中都实现了元素个数的变化,并且对元素个数的变化进行了相应记录。所以在这个方法中只需要将记录元素个数的变量size返回即可。

演示代码

        实现了上述的所有方法,接下来要对其进行验证,所以开辟一个main方法,在main方法中实例化一个单向链表类并测试以上的四个方法。具体实现如下所示:

package com.data.demo;/***基于单项链表实现元素存取的容器类* @param <E>*/
public class MySinglyLinkedList<E> implements MyList<E> {//定义单向链表中的节点对象class Node<E>{private E item;//存储元素private Node next;//存储下一个节点对象的地址Node(E item,Node next){this.item = item;this.next = next;}}private Node head;//存放链表的头节点private int size;//记录元素个数//添加元素@Overridepublic void add(E element) {//创建节点Node<E> node = new Node<>(element,null);//找到尾节点Node tail = getTail();//节点挂接if(tail == null){this.head = node;}else{tail.next = node;}//记录元素个数this.size++;}//寻找尾节点的方法private Node getTail(){//判断头节点是否存在if(this.head == null){return  null;}Node node  = this.head;while(true){if(node.next == null)break;node = node.next;}return node;}//获取元素@Overridepublic E get(int index) {//校验index的合法性this.checkIndex(index);//根据位置获取节点元素Node<E> node = this.getNode(index);//返回节点中的元素return node.item;}/*** 校验index合法性的方法* @return*/
private void checkIndex(int index){if(!(index < this.size && index >= 0)){throw new IndexOutOfBoundsException("Index:"+index+"Size:"+this.size);}
}/*** 根据位置获取节点* @return*/private Node getNode(int index){Node node = this.head;for(int i = 0;i < index;i++){node = node.next;}return node;}//获取元素个数@Overridepublic int size() {return this.size;}//删除元素@Overridepublic E remove(int index) {//校验index的合法性this.checkIndex(index);//根据位置找到节点对象Node<E> node = getNode(index);//获取该节点对象中的元素E item = node.item;//将该节点对象从单向链表中删除//判断该节点是否为头节点if(this.head == node){this.head = node.next;}else{Node<E> temp = this.head;for (int i = 0; i < index-1; i++) {temp = temp.next;}temp.next = node.next;}node.next = null;//记录元素个数this.size--;//返回该元素return item;}public static void main(String[] args) {MySinglyLinkedList<String> mySinglyLinkedList = new MySinglyLinkedList<>();mySinglyLinkedList.add("a");mySinglyLinkedList.add("b");mySinglyLinkedList.add("c");mySinglyLinkedList.add("d");mySinglyLinkedList.add("e");System.out.println(mySinglyLinkedList.size());System.out.println(mySinglyLinkedList.remove(2));for (int i = 0; i < mySinglyLinkedList.size; i++) {System.out.println(mySinglyLinkedList.get(i));}}
}

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

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

相关文章

鸿蒙:路由Router原理

页面路由&#xff1a;在应用程序中实现不同页面之间的跳转和数据传递 典型应用&#xff1a;商品信息返回、订单等多页面跳转 页面栈最大容量为32个页面&#xff0c;当页面需要销毁可以使用router.clear()方法清空页面栈 router有两种页面跳转模式&#xff1a; router.pushUrl…

入门Axure:快速掌握原型设计技能

2002 年&#xff0c;维克托和马丁在旧金山湾区的一家初创公司工作&#xff0c;发现自己一再被软件开发生命周期的限制所困扰&#xff0c;而且产品团队在编写规范之前很难评估他们的解决方案&#xff0c;开发人员经常不理解&#xff08;或不阅读&#xff09;给出的规范&#xff…

线程池666666

1. 作用 线程池内部维护了多个工作线程&#xff0c;每个工作线程都会去任务队列中拿取任务并执行&#xff0c;当执行完一个任务后不是马上销毁&#xff0c;而是继续保留执行其它任务。显然&#xff0c;线程池提高了多线程的复用率&#xff0c;减少了创建和销毁线程的时间。 2…

如何指定Microsoft Print To PDF的输出路径

在上一篇文章中&#xff0c;介绍了三种将文件转换为PDF的方式。默认情况下&#xff0c;在Microsoft Print To PDF的首选项里&#xff0c;是看不到输出路径的设置的。 需要一点小小的手段。 运行输入 control 打开控制面板&#xff0c;选择硬件和声音下的查看设备和打印机 找到…

ardupilot开发 --- 坐标变换 篇

Good Morning, and in case I dont see you, good afternoon, good evening, and good night! 0. 一些概念1. 坐标系的旋转1.1 轴角法1.2 四元素1.3 基于欧拉角的旋转矩阵1.3.1 单轴旋转矩阵1.3.2 多轴旋转矩阵1.3.3 其他 2. 齐次变换矩阵3. visp实践 0. 一些概念 相关概念&am…

github仓库的基本使用-创建、上传文件、删除

1.第一步 先点击左侧菜单栏的远程仓库 2.点击NEW 3.创建仓库 然后点击右下角的 CREATE 4.点击code 点击SSH,然后我出现了You don’t have any public SSH keys in your GitHub account. You can add a new public key, or try cloning this repository via HTTPS. 1&#xff…

你喜欢波段交易吗?

波段交易的核心在于精准捕捉市场中的长期趋势波动&#xff0c;以实现更为稳健的收益。与剥头皮和日内交易不同&#xff0c;波段交易者更倾向于持有交易头寸数日乃至数周&#xff0c;以更宽广的视角把握市场动态。 这种交易方式的优势在于&#xff0c;它降低了对即时市场反应的…

JavaWeb系列三: JavaScript学习 下

文章目录 js数组定义方式数组遍历 js函数函数入门函数使用方式使用方式一使用方式二 函数注意事项函数练习题 定义对象使用object定义使用{}定义 事件onload事件onclick事件失去焦点事件内容发生改变事件表单提交事件静态注册动态注册表单作业 dom对象文档对象模型document对象…

Linux --账号和权限管理

目录 1、 管理用户账号和组账概述 1.1 用户账号分类 1.2 组账号 1.3 UID 和 GID 2、用户账号文件 2.1 passwd 2.2 shadow 3、管理目录和文件属性 3.1 chage 命令 3.2 useradd 命令 3.3 passwd 命令 ​编辑3.4 usermod 命令 3.5 userdel 命令 4、用户账户的初始配置…

爬数据是什么意思?

爬数据的意思是&#xff1a;通过网络爬虫程序来获取需要的网站上的内容信息&#xff0c;比如文字、视频、图片等数据。网络爬虫&#xff08;网页蜘蛛&#xff09;是一种按照一定的规则&#xff0c;自动的抓取万维网信息的程序或者脚本。 学习一些爬数据的知识有什么用呢&#x…

分解+降维+预测!多重创新!直接写核心!EMD-KPCA-Transformer多变量时间序列光伏功率预测

分解降维预测&#xff01;多重创新&#xff01;直接写核心&#xff01;EMD-KPCA-Transformer多变量时间序列光伏功率预测 目录 分解降维预测&#xff01;多重创新&#xff01;直接写核心&#xff01;EMD-KPCA-Transformer多变量时间序列光伏功率预测效果一览基本介绍程序设计参…

[数据集][目标检测]水面垃圾水面漂浮物检测数据集VOC+YOLO格式3749张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;3749 标注数量(xml文件个数)&#xff1a;3749 标注数量(txt文件个数)&#xff1a;3749 标注…

聊聊etsy平台,一个年入百万的项目

聊聊etsy平台&#xff0c;一个年入百万的项目 什么是etsy,这是怎样一个平台&#xff0c;怎样盈利的&#xff1f;相信现在大家满脑子都是这些疑问。 这个平台也是无意间一个学员提到的&#xff0c;据说他朋友靠这个平台年赚好几百万。苦于门槛太高&#xff0c;他也做不了。今天…

web权限到系统权限 内网学习第一天 权限提升 使用手工还是cs???msf可以不??

现在开始学习内网的相关的知识了&#xff0c;我们在拿下web权限过后&#xff0c;我们要看自己拿下的是什么权限&#xff0c;可能是普通的用户权限&#xff0c;这个连添加用户都不可以&#xff0c;这个时候我们就要进行权限提升操作了。 权限提升这点与我们后门进行内网渗透是乘…

ATFX汇市:欧元区CPI与失业率数据同时发布,欧元或迎剧烈波动

ATFX汇市&#xff1a;CPI数据是中央银行决策货币政策的主要依据&#xff0c;失业率数据是中央银行判断劳动力市场健康状况的核心指标。欧元区的CPI和失业率数据将在今日17:00同时发布&#xff0c;在欧央行6月6日降息一次的背景下&#xff0c;两项数据将显著影响国际市场对欧央行…

问题-小技巧-Win11的常用快捷方式和有用快捷方式

文章目录 常用快捷方式1、CtrlA 全部选中2、Ctrl Z 撤销3、Ctrl X 剪切4、Ctrl C 粘贴5、Ctrl V 复制6、winshifts截图&#xff0c;Windows系统自带截图工具&#xff0c;功能太少7、ctrlshifts截图&#xff0c;edge自带截图工具&#xff0c;使用时需要打开edge8、 winv 可以查看…

C盘清理和管理

本篇是C盘一些常用的管理方法&#xff0c;以及定期清理C盘的方法&#xff0c;大部分情况下都能避免C盘爆红。 C盘清理和管理 C盘存储管理查看存储情况清理存储存储感知清理临时文件清理不需要的 迁移存储 磁盘清理桌面存储管理应用存储管理浏览器微信 工具清理 C盘存储管理 查…

C#的五大设计原则-solid原则

什么是C#的五大设计原则&#xff0c;我们用人话来解释一下&#xff0c;希望小伙伴们能学会&#xff1a; 好的&#xff0c;让我们以一种幽默的方式来解释C#的五大设计原则&#xff08;SOLID&#xff09;&#xff1a; 单一职责原则&#xff08;Single Responsibility Principle…

通过容器启动QAnything知识库问答系统

QAnything (Question and Answer based on Anything) 是致力于支持任意格式文件或数据库的本地知识库问答系统&#xff0c;可断网安装使用。目前已支持格式&#xff1a;PDF(pdf)&#xff0c;Word(docx)&#xff0c;PPT(pptx)&#xff0c;XLS(xlsx)&#xff0c;Markdown(md)&…

2024年教育政策与实践研讨会(ICEPP 2024)

随着全球化的不断深入&#xff0c;教育作为国家发展的基石&#xff0c;其政策与实践的探讨愈发显得重要。为此&#xff0c;备受瞩目的教育政策与实践研讨会&#xff08;ICEPP 2024&#xff09;将于2024年11月8日至10日在中国武汉隆重举行。此次会议汇聚了国内外众多专家学者&am…