数据结构 之 链表LinkedList

目录

1. ArrayList的缺陷:

2. 链表:

2.1 链表的概念及结构:

 3. 链表的使用和模拟实现:

3.1 构造方法:

3.2 模拟实现:

4. 源码分享:


在我学习顺序表之后,我就立马开始了链表的学习,但是在学习链表之前,我就有一个疑问,为什么明明有了顺序表这一种数据结构为什么我们还要有链表这一种数据结构呢?

1. ArrayList的缺陷:

通过对ArrayList的简单了解,我们知道,其实顺序表的底层是由数组来实现的,他是一段连续的空间,所以,当ArrayList在增删元素的时候,通过计算我们发现,他的时间复杂度为O(n),效率比较低下,如果数据很大的情况下,使用顺序表进行增删操作,会浪费非常多的时间,所以,就引入了链表这一种数据结构!!!

2. 链表:

2.1 链表的概念及结构:

链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。

在现实生活中,火车的结构就类似于我们的链表

链表的结构多种多样:

1. 单向或者双向:

2. 带头或者不带头:

3.循环或者不循环:

以上就是链表的结构,所以一共有八种链表:

单向不带头不循环链表;

单向带头不循环链表;

单向不带头循环链表;

单向带头循环链表;

双向不带头不循环链表;

双向带头不循环链表;

双向不带头循环链表;

双向带头循环链表;

 3. 链表的使用和模拟实现:

3.1 构造方法:

链表源码提供了两个构造方法:

这是不带参数的构造方法;

这是带一个参数的构造方法,将c中的全部元素都拷贝到链表中;

3.2 模拟实现:

首先,我们需要创建一个My_LinkedList类:(我们以单向不带头不循环链表为例)

在这个类中创建一个静态内部类,称为ListNode,一共有两个成员,一个是value用来存放该节点的值的,一个是next,用来存放下一个节点的地址,同时我们还可以写一个构造方法;

再实例化一个头节点

public class My_LinkedList {static class ListNode {public int val;//数值域public ListNode next;//存储下一个节点的地址public ListNode(int val) {this.val = val;}}public ListNode head = new ListNode(0);   //实例化头节点public My_LinkedList(int val) {head.val = val;                       //构造方法}
}

< 1 > addFirst方法:

头插法,在链表的第一个节点插入新的节点:

假如我们在如图所示的链表中头插一个node节点

为了防止我们的首节点丢失,我们需要先将首节点的地址传给新的节点,再将新节点更新为head

/*** 头插法* addFrist*/public void addFirst(int data) {ListNode node = new ListNode(data);     //实例化一个节点node/*if(this.head == null) {this.head = node;}else {node.next = this.head;this.head = node;}*/node.next = this.head;              //将首节点的地址赋给新的节点this.head = node;                   //将新的节点更新为head}

< 2 > addLast方法:

尾插法,将新节点插入链表末端:

public void addLast(int data) {ListNode node = new ListNode(data);     //实例化一个node节点if (head == null) {head = node;                        //若链表为空,则直接将node更新为head} else {ListNode cur = head;                //实例化一个cur节点while (cur.next != null) {          //用cur节点去遍历链表,知道cur节点为最后一个节点cur = cur.next;                 }//cur.next == null;cur.next = node;                    //将node赋值给cur.next,也就是将node节点放在了链表的最末端}}

< 3 > size方法:

得到并返回单链表的长度:

//得到单链表的长度public int size() {int count = 0;              //创建整形变量count作为计数器ListNode cur = this.head;   //实例化cur = 当前的头节点while (cur != null) {       //遍历整个链表count++;                //每遍历一个节点,则count++cur = cur.next;         //cur一直向后移动}return count;               //返回单链表的长度}

< 4 > add方法:

任意位置插入,将节点插入指定位置:

在插入之前,我先要检测给定的节点位置是否合理:

private void checkIndexAdd(int index) {if (index < 0 || index > size()) {          //如果位置不合理throw new MySingleListIndexOutOfException("任意位置插入的时候,index不合法!");}           //若不合理,则抛出异常}
MySingleListIndexOutOfException :
public class MySingleListIndexOutOfException extends RuntimeException{public MySingleListIndexOutOfException() {super();}public MySingleListIndexOutOfException(String str) {super(str);}
}
public void add(int index, int data) throws MySingleListIndexOutOfException {checkIndexAdd(index);                   //检查index位置是否合法if (index == 0) {                       //如果index为0addFirst(data);                     //进行头插法return;}if (index == size()) {                  //如果index = 单链表长度addLast(data);                      //进行尾插法return;}ListNode node = new ListNode(data);     //实例化一个node节点,值为dataListNode cur = findIndexSubOne(index);  //找到index下标的前一个节点node.next = cur.next;                   //为了防止节点丢失,先将要插入节点的next更新为前一个节点的原来的下一个节点的地址cur.next = node;                        //将cur的next更新为新的节点的地址}
private ListNode findIndexSubOne(int index) {ListNode cur = this.head;           //实例化一个cur = 头节点while (index - 1 != 0) {            //向后遍历index - 1次,找到index下标节点的前一个节点cur = cur.next;index--;}return cur;                         //返回当前节点 }

< 5 > contains方法:

查找是否包含关键子key在链表当中:

//查找是否包含关键字key是否在单链表当中public boolean contains(int key) {if (head == null) {                 //如果head为空,则直接返回falsereturn false;}ListNode cur = this.head;           //实例化一个cur节点 = 头节点//cur != null 说明 没有遍历完 链表while (cur != null) {               //cur不停向后遍历if (cur.val == key) {           //找到了keyreturn true;                //返回true}cur = cur.next;}return false;                       //循环结束,cur.next = null,说明没有找到key,返回false}

< 6 > remove方法:

删除第一次出现的值为key的节点:

//删除第一次出现关键字为key的节点public void remove(int key) {if (this.head == null) {                            //如果链表为空,则没有元素,无法删除System.out.println("此时链表为空,不能进行删除!");return;}if (this.head.val == key) {//判断 第一个节点是不是等于我要删除的节点this.head = this.head.next;return;}ListNode cur = this.head;           //实例化一个cur = headwhile (cur.next != null) {          //遍历整个链表,直到cur为最后一个节点为止if (cur.next.val == key) {      //如果找到了值为key的节点//进行删除了ListNode del = cur.next;    //实例化del节点为cur节点的下一个节点cur.next = del.next;        //将前一个节点的next更新为后一个节点的地址return;}cur = cur.next;}System.out.println("未找到值为key的节点");      //跳出循环,说明没有值为key的节点}

< 7 > removeAll方法:

//删除所有值为key的节点public void removeAllKey(int key) {if (this.head == null) {                    //如果head == null,则链表为空,不能进行删除操作return;}//单独处理了头节点,若头节点的值为key,则头节点向后移动if(this.head.val == key) {head = head.next;}ListNode cur = this.head.next;              //实例化cur节点 == head节点的下一个节点ListNode prev = this.head;                  //实例化prev节点 == head节点while (cur != null) {                       //cur节点不断向后遍历if (cur.val == key) {                   //如果cur.val == key即找到了值为key的节点prev.next = cur.next;               //将prev节点即cur的前一个节点的next = cur.next, 即删除了cur位置的节点cur = cur.next;                     //cur继续向后走,查找值为key的节点} else {                                //若cur.val != keyprev = cur;                         //prev和cur一起向后移动                cur = cur.next;}}}

< 8 > clear方法:

clear方法的实现有两种方法:

第一种:

public void clear() {this.head = null;}

这种做法比较粗暴,直接将head置为空,由于之前的链表中的节点没有了引用,故会被系统给自动回收;

第二种:

/*** 当我们 执行clear这个函数的时候,会将这个链表当中的所有的节点回收*/public void clear() {ListNode cur = this.head;           //令cur = headListNode curNext = null;            //实例化一个curNextwhile (cur != null) {               curNext = cur.next;             //将curNext更新为cur.nextcur.next = null;                //再将cur节点的next置为空cur = curNext;                  //再将cur更新为curNext,一个一个的去删除链表中的所有的节点}head = null;                        //最后将head置为空即可}

4. 源码分享:

在我的模拟实现源码中,我多写了createList方法和display方法,即创建链表和打印链表方法,为的是模拟实现后方便进行测试,以找出代码的不足!!!

public class My_LinkedList {static class ListNode {public int val;//数值域public ListNode next;//存储下一个节点的地址public ListNode(int val) {this.val = val;}}public ListNode head;//代表单链表的头结点的引用/*** 这里只是简单的进行,链表的构造。*/public void createList() {ListNode listNode1 = new ListNode(12);ListNode listNode2 = new ListNode(23);ListNode listNode3 = new ListNode(34);ListNode listNode4 = new ListNode(45);ListNode listNode5 = new ListNode(56);listNode1.next = listNode2;listNode2.next = listNode3;listNode3.next = listNode4;listNode4.next = listNode5;this.head = listNode1;}public void display() {ListNode cur = head;while (cur != null) {System.out.print(cur.val + " ");cur = cur.next;}System.out.println();}/*** 从指定的节点开始答应** @param newHead*/public void display(ListNode newHead) {ListNode cur = newHead;while (cur != null) {System.out.print(cur.val + " ");cur = cur.next;}System.out.println();}/*** 头插法* addFrist*/public void addFirst(int data) {ListNode node = new ListNode(data);     //实例化一个节点node/*if(this.head == null) {this.head = node;}else {node.next = this.head;this.head = node;}*/node.next = this.head;              //将首节点的地址赋给新的节点this.head = node;                   //将新的节点更新为head}//尾插法 O(n)public void addLast(int data) {ListNode node = new ListNode(data);     //实例化一个node节点if (head == null) {head = node;                        //若链表为空,则直接将node更新为head} else {ListNode cur = head;                //实例化一个cur节点while (cur.next != null) {          //用cur节点去遍历链表,知道cur节点为最后一个节点cur = cur.next;}//cur.next == null;cur.next = node;                    //将node赋值给cur.next,也就是将node节点放在了链表的最末端}}public void add(int index, int data) throws MySingleListIndexOutOfException {checkIndexAdd(index);                   //检查index位置是否合法if (index == 0) {                       //如果index为0addFirst(data);                     //进行头插法return;}if (index == size()) {                  //如果index = 单链表长度addLast(data);                      //进行尾插法return;}ListNode node = new ListNode(data);     //实例化一个node节点,值为dataListNode cur = findIndexSubOne(index);  //找到index下标的前一个节点node.next = cur.next;                   //为了防止节点丢失,先将要插入节点的next更新为前一个节点的原来的下一个节点的地址cur.next = node;                        //将cur的next更新为新的节点的地址}/*** 找到index-1位置的节点** @param index* @return 该节点的地址*/private ListNode findIndexSubOne(int index) {ListNode cur = this.head;           //实例化一个cur = 头节点while (index - 1 != 0) {            //向后遍历index - 1次,找到index下标节点的前一个节点cur = cur.next;index--;}return cur;                         //返回当前节点}private void checkIndexAdd(int index) {if (index < 0 || index > size()) {          //如果位置不合理throw new MySingleListIndexOutOfException("任意位置插入的时候,index不合法!");}           //若不合理,则抛出异常}//查找是否包含关键字key是否在单链表当中public boolean contains(int key) {if (head == null) {                 //如果head为空,则直接返回falsereturn false;}ListNode cur = this.head;           //实例化一个cur节点 = 头节点//cur != null 说明 没有遍历完 链表while (cur != null) {               //cur不停向后遍历if (cur.val == key) {           //找到了keyreturn true;                //返回true}cur = cur.next;}return false;                       //循环结束,cur.next = null,说明没有找到key,返回false}//删除第一次出现关键字为key的节点public void remove(int key) {if (this.head == null) {                            //如果链表为空,则没有元素,无法删除System.out.println("此时链表为空,不能进行删除!");return;}if (this.head.val == key) {//判断 第一个节点是不是等于我要删除的节点this.head = this.head.next;return;}ListNode cur = this.head;           //实例化一个cur = headwhile (cur.next != null) {          //遍历整个链表,直到cur为最后一个节点为止if (cur.next.val == key) {      //如果找到了值为key的节点//进行删除了ListNode del = cur.next;    //实例化del节点为cur节点的下一个节点cur.next = del.next;        //将前一个节点的next更新为后一个节点的地址return;}cur = cur.next;}System.out.println("未找到值为key的节点");      //跳出循环,说明没有值为key的节点}//删除所有值为key的节点public void removeAllKey(int key) {if (this.head == null) {                    //如果head == null,则链表为空,不能进行删除操作return;}//单独处理了头节点,若头节点的值为key,则头节点向后移动if(this.head.val == key) {head = head.next;}ListNode cur = this.head.next;              //实例化cur节点 == head节点的下一个节点ListNode prev = this.head;                  //实例化prev节点 == head节点while (cur != null) {                       //cur节点不断向后遍历if (cur.val == key) {                   //如果cur.val == key即找到了值为key的节点prev.next = cur.next;               //将prev节点即cur的前一个节点的next = cur.next, 即删除了cur位置的节点cur = cur.next;                     //cur继续向后走,查找值为key的节点} else {                                //若cur.val != keyprev = cur;                         //prev和cur一起向后移动cur = cur.next;}}}//得到单链表的长度public int size() {int count = 0;              //创建整形变量count作为计数器ListNode cur = this.head;   //实例化cur = 当前的头节点while (cur != null) {       //遍历整个链表count++;                //每遍历一个节点,则count++cur = cur.next;         //cur一直向后移动}return count;               //返回单链表的长度}/*** 当我们 执行clear这个函数的时候,会将这个链表当中的所有的节点回收*/public void clear() {//this.head = null;//这种做法 比较粗暴!ListNode cur = this.head;           //令cur = headListNode curNext = null;            //实例化一个curNextwhile (cur != null) {curNext = cur.next;             //将curNext更新为cur.nextcur.next = null;                //再将cur节点的next置为空cur = curNext;                  //再将cur更新为curNext,一个一个的去删除链表中的所有的节点}head = null;                        //最后将head置为空即可}
}

以上就是链表的所有的内容了,感谢大家的观看!!!

制作不易,三连支持

谢谢!!!

以上的模拟实现代码未必是最优解,仅代表本人的思路,望多多理解,谢谢!!

最后送给大家一句话,同时也是对我自己的勉励:

在心里种花,人生才不会荒芜!!!!

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

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

相关文章

鸿蒙开发:从入门到精通的全方位学习指南

随着华为鸿蒙HarmonyOS生态系统的迅速扩展&#xff0c;越来越多的开发者渴望深入了解并掌握这一前沿技术。本文旨在为鸿蒙开发新手提供一份详尽且实用的学习教程&#xff0c;助您从零开始&#xff0c;逐步迈向鸿蒙开发的巅峰。 一、鸿蒙开发环境搭建 DevEco Studio安装&#x…

非接触型红外线(IR)温度传感器 为AI NB打造更舒适工作环境

生成式AI掀起全球热潮,各大计算机厂纷纷推出AI NB/PC新品,不过AI新机也同时面临的电源相关热处理的难题,众智光电科技于今日(19号)提出最佳创新解决方案,以非接触型红外线(IR)温度感测芯片突破NB应用,能让使用者有全新体验,打造更舒适的工作环境。 在现今科技迅速发展的时代,高…

力扣--深度优先算法/回溯算法90.子集Ⅱ

思路分析&#xff1a; 成员变量&#xff1a; result: 用于存储最终的子集结果。path: 用于存储当前正在构建的子集。 DFS函数&#xff1a; dfs(vector<int>& nums, int start): 递归地生成子集。 从给定的start索引开始遍历数组。如果当前元素与前一个元素相同&#…

spring boot 使用 webservice

spring boot 使用 webservice 使用 java 自带的 jax-ws 依赖 如果是jdk1.8,不需要引入任何依赖&#xff0c;如果大于1.8 <dependency><groupId>javax.jws</groupId><artifactId>javax.jws-api</artifactId><version>1.1</version&g…

前端的数据标记协议

文章目录 数据标记协议是什么数据标记协议的作用常见的数据标记协议Open Graph protocol 开放图谱协议基本元数据协议可选元数据结构化属性 —— 元数据的属性多个相同的元数据标签类型元数据的使用方法全局类型使用自定义类型使用对象类型使用歌曲对象类型视频对象类型文章对象…

webgl instance 绘制

webgl instance 绘制 效果: key1: 创建实例缓存 function createMesh() {for (let i 0; i < NUM_CUBE; i) {const angle i * 2 * Math.PI / NUM_CUBE;const x Math.sin(angle) * RADIUS;const y 0;const z Math.cos(angle) * RADIUS;cubes[i] {scale: new THREE.V…

Rust 使 Python 函数速度提高 5000%

大家应该都听说过&#xff0c;Rust 因其卓越的性能和安全性&#xff0c;正被越来越多的科技巨头采用&#xff0c;推荐开发者使用Rust来构建关键软件。 今天&#xff0c;来深入学习一下&#xff0c;如何利用 Rust 来大幅提升你的 Python代码性能&#xff01; 寻找第N个质数&…

Node.js作用

Node.js可以开发应用 开发服务器应用 开发工具类应用 开发桌面端应用

Linux查看磁盘命令df-h详解

df -h 是一个常用的 Linux 命令&#xff0c;用于查看文件系统的磁盘使用情况并以易于阅读的方式显示。以下是 df -h 命令的详细解释&#xff1a; -h&#xff1a;以人类可读的格式显示磁盘空间大小。例如&#xff0c;使用 GB、MB、KB 等单位代替字节。 执行 df -h 命令后&…

【webrtc】m122:PacingController 源码阅读

PacingController 关系与BitrateProber 关系更为密切PacingController 内置BitrateProber G:\CDN\signalapp_webrtc\modules\pacing\pacing_controller.hPacingControllerBitrateProber prober_;PacingController 关系与BitrateProber 关系更为密切,在整个系统中的地位也更重要…

阿里云2核4G服务器ECS规格清单、CPU性能详解和租用价格表

阿里云2核4G服务器多少钱一年&#xff1f;2核4G服务器1个月费用多少&#xff1f;2核4G服务器30元3个月、85元一年&#xff0c;轻量应用服务器2核4G4M带宽165元一年&#xff0c;企业用户2核4G5M带宽199元一年。本文阿里云服务器网整理的2核4G参加活动的主机是ECS经济型e实例和u1…

C++关于socket中收发数据不完整问题

1、socket缓冲区 每一个socket在被创建之后&#xff0c;系统都会给它分配两个缓冲区&#xff0c;即输入缓冲区和输出缓冲区。 &#xff08;1&#xff09;send函数并不是直接将数据传输到网络中&#xff0c;而是负责将数据写入输出缓冲区&#xff0c;数据从输出缓冲区发送到目标…

【电路笔记】-晶体管作为开关

晶体管作为开关 文章目录 晶体管作为开关1、概述2、截止区域3、饱和区域4、示例5、晶体管开关类型及应用5.1 数字逻辑晶体管开关5.2 PNP晶体管开关5.3 达林顿晶体管开关6、总结1、概述 晶体管开关可用于通过使用处于饱和或截止状态的晶体管来打开或关闭低压直流设备(例如 LED…

python 蓝桥杯 之 字符串

文章目录 题目一find(str,start,end) 函数 题目一 find(str,start,end) 函数 在Python中&#xff0c;find()函数用于在字符串中查找子字符串&#xff0c;并返回第一次出现的子字符串的索引。如果找不到子字符串&#xff0c;则返回-1。find()函数的语法如下&#xff1a; str.f…

OpenHarmony开发—购物示例应用

介绍 本示例展示在进场时加载进场动画&#xff0c;整体使用Tabs容器设计应用框架&#xff0c;通过TabContent组件设置分页面&#xff0c;在子页面中绘制界面。通过Navigation完成页面之间的切换。在详情页中通过 Video组件加载视频资源&#xff0c;使用CustomDialogController…

探索 PostgreSQL 的高级数据类型 - 第二部分

范围类型 范围类型提供了一种简洁的方式来表示单个数据库字段中的值范围。它们在从时间数据到数字间隔的各种领域中都有应用。在本篇博客文章中&#xff0c;我们将通过 DML/SQL 语句和 Navicat for PostgreSQL 16 来深入了解它们的使用&#xff08;以及好处&#xff01;&#…

9个免费游戏后端平台

在这篇文章中&#xff0c;您将看到 九个免费的游戏服务平台提供商&#xff0c;这可以帮助您开始在线多人游戏&#xff0c;而无需预先投入大量资金。 每个提供商都有非常独特的功能&#xff0c;因此成本应该只是决定时要考虑的方面之一。 我还从低预算项目的角度对免费提供商进…

信钰证券|AI用电需求井喷 新能源板块“火花”四溅

近日&#xff0c;“ChatGPT耗电量惊人”登上热搜。据估算&#xff0c;ChatGPT每天要响应大约2亿个请求&#xff0c;日耗费电力超越50万度&#xff0c;相当于1.7万个美国家庭的用电量。 AI越智能&#xff0c;需要处理的信息就越多&#xff0c;所需电力也将呈几何级增加。马斯克…

SpringBoot注解--08--注解@JsonInclude

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 JsonInclude注解是jackSon中最常用的注解之一&#xff0c;是为实体类在接口序列化返回值时增加规则的注解 1.JsonInclude用法2.JsonInclude注解中的规则有 案例需求…

Kubernetes--ingress实现七层负载

目录 一、传统方式&#xff1a;不借助ingress实现七层代理 二、nginx-ingress 三、使用ingress实现七层代理 四、部署ingrss-nginx及功能 五、样例 1.Ingress-nginx HTTP代理访问 2.Ingress HTTPS代理访问&#xff08;会话卸载层&#xff09; 3.Nginx进行BasicAuth&…