[JS与链表]普通链表

news/2023/11/28 15:49:15

为什么要用链表

要储存一系列数据,最常用的数据结构是数组。数组有个缺点就是在中间插入或删除元素需要移动元素,成本很高。

什么是链表

链表也是有序元素的集合结构。链表中的元素在内存中并不是连续放置的。每个元素都可以理解为一个对象。包含了本身元素的值,和一个指向下一个元素的引用。

在内存的堆栈中假想:

现实也有一些链表的例子:

① 芭蕾舞队

中间插入一个芭蕾演员,需要:被插入点前一个演员牵(next),同时要牵住下一个演员(自身的next)

②火车挂载货厢

链表的好处

在于添加或移除元素的时候不需要移动其他元素。

但是在数组中我们可以通过指针直接访问任何位置的任何元素。

而若要访问链表中间的一个元素,则需要从表头开始迭代链表直到找到所需的元素。

创建链表

通过上面的例子,我们知道链表是由一块一块的节点链接起来。

所以我们需要先创建节点Node类

class Node {constructor(ele) {this.element = ele;// 暴露到外面赋值this.next = undefined;}
}
  • element代表当前链表元素的值

  • next属性指向链表下一个元素

然后我们来实现LinkedList类

class LinkedList {constructor() {this.count = 0;this.head = undefined;}
}

我们用count属性来统计链表元素的数量。用head来记录链表头端的首个元素。

在链表尾部添加元素

①链表为空,添加的是第一个元素。

②链表不为空,迭代找到最后一个元素,向其追加元素。

push(element) {const node = new Node(element);// 注意,这里是== null,==null会囊括===undefined和====null两种结果// 链表最后一个节点的下一个元素(next)始终是undefined或nullif (this.head == null) {this.head = node;}else {let nextNode = this.head;while (nextNode.next!=null) {nextNode = nextNode.next;}nextNode.next = node;}this.count++;
}

从链表中移除元素(按照索引)

还是两种场景:

① 移除链表头部第一个元素。

可以看到只要把head赋值给第二个元素就好了。之前的head节点不需要删除,等待js的垃圾回收机制回收它(没有被引用。)

② 移除链表除头部之外其他元素。

由图示,我们只需要断开目标节点的next,并把目标节点前一个节点的next挂载到下一个节点的value即可。也就是说,我们需要获取剔除节点的前一个节点即可。

removeAt(index) {// 错误场景if (index>0 && index >=this.count) {return undefined}this.count --;// 移除第一项if (index === 0) {const throwEle = this.head.element;this.head = this.head.next;return throwEle;}else {// 移除中间项,只需要找到剔除节点的的前一个节点// 前一个节点let preNode;// 当前节点let currentNode = this.head;// 找到删除项for (var i =0;i<index;i++) {preNode = currentNode;currentNode = currentNode.next;}preNode.next = currentNode.next;return currentNode.element}
}

回头优化代码

既然我们能够在remove方法里通过index下标定位到链表的元素,那我们可以把定位的逻辑抽离出来作为内部公共方法。

getNodeAt(index) {if (index>0 && index >=this.count) {return undefined}if (this.index === 0) {return this.head}let currentNode = this.head;for (var i =0;i<index;i++) {currentNode = currentNode.next}return currentNode
}
removeAt(index) {// 错误场景if (index>0 && index >=this.count) {return undefined}let delNode;delNode = this.getNodeAt(index)// 移除第一项if (index === 0) {this.head = this.head.next;}else {let preNode = this.getNodeAt(index - 1);preNode.next = delNode.next;}this.count --;return delNode
}

在链表任意位置插入元素

还是两种情况:表头与表中

insertAt(ele,index) {if (index < 0 || index > this.count) {return false}const newNode = new Node(ele)if (index === 0) {const originHead = this.head;this.head = newNode;this.head.next = originHead}else {const originNode = this.getNodeAt(index);const preNode = this.getNodeAt(index - 1);preNode.next = newNode;newNode.next = originNode;}this.count ++;return true
}

就算是在结尾插入也是符合第二种场景。

返回元素在链表中的位置

思路类似于数组的indexof,通过迭代链表里的元素对比传参元素,若存在则返回下标,不存在就返回-1。

首先,我们需要定义比较函数。如果我们链表节点储存的element是数字或字符串等基本数据类型,我们可以使用a === b作为比较函数传入。但是如果是储存复杂的引用对象,我们必须开放让使用者自定义比较函数的接口。

// 以基本类型的数据对比为例
function compareFn(a,b) {return a === b
}
class LinkedList {constructor(equalFn=compareFn) {this.count = 0;this.head = undefined;this.equalFn = equalFn;}
}

可以看到,我们给予了LinkedList类一个默认的比较函数。同时也支持用户在生成实例的时候传入自定义的比较函数,以适应自身的比较需求。

indexOf(ele) {let currentNode = this.head;for (var i = 0 ;i < this.count;i++) {if (this.euqalFn(currentNode.element,ele)) {return i;}else {currentNode = currentNode.next;}}return -1
}

从链表中移除元素(指定值)

我们上面已经实现了根据索引删除链表中的元素。现在要实现根据指定的值移除元素,我们需要两步:

①通过指定的元素值找到它在链表中的下标。

②根据下标删除链表中的元素。

remove(ele){const index = this.indexOf(ele);return this.removeAt(index)
}

获取链表长度

size() {return this.count}

判断链表是否为空

isEmpty() {return this.size() === 0
}

获取头部元素

getHead() {return this.head;
}

toString方法

此方法会把LinkedList对象转换成一个字符串。

toString() {if (this.head == null) {return ''}let currentNode = this.head;let objStr = ''for (var i = 0 ;i < this.count;i++) {objStr+=`[下标为${i},值为${currentNode.element}]`currentNode = currentNode.next;}
}

代码整理

class Node {constructor(ele) {this.element = ele;this.next = undefined;}
}
function compareFn(a,b) {return a === b
}
class LinkedList {constructor(equalFn=compareFn) {this.count = 0;this.head = undefined;this.equalFn = equalFn;}toString() {if (this.head == null) {return ''}let currentNode = this.head;let objStr = ''for (var i = 0 ;i < this.count;i++) {objStr+=`[下标为${i},值为${currentNode.element}]`currentNode = currentNode.next;}}remove(ele){const index = this.indexOf(ele);return this.removeAt(index)}indexOf(ele) {let currentNode = this.head;for (var i = 0 ;i < this.count;i++) {if (this.equalFn(currentNode.element,ele)) {return i;}else {currentNode = currentNode.next;}}return -1}insertAt(ele,index) {if (index < 0 || index > this.count) {return false}const newNode = new Node(ele)if (index === 0) {const originHead = this.head;this.head = newNode;this.head.next = originHead}else {const originNode = this.getNodeAt(index);const preNode = this.getNodeAt(index - 1);preNode.next = newNode;newNode.next = originNode;}this.count ++;return true}getNodeAt(index) {if (index>0 && index >=this.count) {return undefined}if (this.index === 0) {return this.head}let currentNode = this.head;for (var i =0;i<index;i++) {currentNode = currentNode.next}return currentNode}removeAt(index) {// 错误场景if (index>0 && index >=this.count) {return undefined}let delNode;delNode = this.getNodeAt(index)// 移除第一项if (index === 0) {this.head = this.head.next;}else {let preNode = this.getNodeAt(index - 1);preNode.next = delNode.next;}this.count --;return delNode}push(element) {const node = new Node(element);// 注意,这里是== null,==null会囊括===undefined和====null两种结果// 链表最后一个节点的下一个元素(next)始终是undefined或nullif (this.head == null) {this.head = node;}else {let nextNode = this.head;while (nextNode.next!=null) {nextNode = nextNode.next;}nextNode.next = node;}this.count++;}
}


http://www.ppmy.cn/news/32604.html

相关文章

【vue.js】在网页中实现一个金属抛光质感的按钮

文章目录前言效果电脑效果手机效果说明完整代码index.html前言 诶&#xff1f;这有一个按钮(&#xff5e;&#xffe3;▽&#xffe3;)&#xff5e;&#xff0c;这是一个在html中实现的具有金属质感并且能镜面反射的按钮~ 效果 电脑效果 手机效果 说明 主要思路是使用 navig…

【链表OJ题(六)】链表分割

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;数据结构 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录链表OJ题(六)1. 链表…

蓝桥杯冲击-02约数篇(必考)

文章目录 前言 一、约数是什么 二、三大模板 1、试除法求约数个数 2、求约数个数 3、求约数之和 三、真题演练 前言 约数和质数一样在蓝桥杯考试中是在数论中考察频率较高的一种&#xff0c;在省赛考察的时候往往就是模板题&#xff0c;难度大一点会结合其他知识点考察&#x…

C++继承[万字详解]

目录 一.继承的介绍 1.1、继承的概念 1.2、继承的定义 1.2.1、定义格式 1.2.2、继承关系和访问限定符 1.2.3、继承基类成员后&#xff0c;在子类中成员访问方式的变化 二.基类和派生类对象赋值转化 三.继承中的作用域 四.派生类的默认成员函数 ★派生类的构造函数 派…

基于libco的c++协程实现(时间轮定时器)

在后端的开发中&#xff0c;定时器有很广泛的应用。 比如&#xff1a; 心跳检测 倒计时 游戏开发的技能冷却 redis的键值的有效期等等&#xff0c;都会使用到定时器。 定时器的实现数据结构选择 红黑树 对于增删查&#xff0c;时间复杂度为O(logn)&#xff0c;对于红黑…

到底什么是线程?线程与进程有哪些区别?

上一篇文章我们讲述了什么是进程&#xff0c;进程的基本调度 http://t.csdn.cn/ybiwThttp://t.csdn.cn/ybiwT 那么本篇文章我们将了解一下什么是线程&#xff1f;线程与进程有哪些区别&#xff1f;线程应该怎么去编程&#xff1f; 目录 http://t.csdn.cn/ybiwThttp://t.csdn…

pm3包1.4版本发布----一个用于3组倾向性评分的R包

目前&#xff0c;本人写的第二个R包pm3包的1.4版本已经正式在CRAN上线&#xff0c;用于3组倾向评分匹配&#xff0c;只能3组不能多也不能少。 可以使用以下代码安装 install.packages("pm3")什么是倾向性评分匹配&#xff1f;倾向评分匹配&#xff08;Propensity Sc…

Python并发与并行

python的多线程因为GIL锁的原因是一个伪多线程 python2:100字节码或I/O阻塞进行切换python3&#xff1a;I/O阻塞进行切换&#xff0c;移除了100字节码切换 1、并发与并行 并行&#xff1a;多个程序同时运行 并发&#xff1a;伪并行&#xff0c;看起来是同时并行&#xff0c;…

SpringMVC拦截器

SpringMVC拦截器 1.什么是拦截器 SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。 **过滤器与拦截器的区别&#xff1a;**拦截器是AOP思想的具体应用。 过滤器 servlet规范中…

嵌入式软件开发之Linux下C编程

目录 前沿 Hello World&#xff01; 编写代码 编译代码 GCC编译器 gcc 命令 编译错误警告 编译流程 Makefile 基础 何为 Makefile Makefile 的引入 前沿 在 Windows 下我们可以使用各种各样的 IDE 进行编程&#xff0c;比如强大的 Visual Studio。但是在Ubuntu 下如何进…

【Kubernetes】第二十八篇 - 实现自动构建部署

一&#xff0c;前言 上一篇&#xff0c;介绍了 Deployment、Service 的创建&#xff0c;完成了前端项目的构建部署&#xff1b; 希望实现&#xff1a;推送代码 -> 自动构建部署-> k8s 滚动更新&#xff1b; 本篇&#xff0c;实现自动构建部署 二&#xff0c;推送触发构…

【C++学习】类和对象(中)一招带你彻底了解六大默认成员函数

前言&#xff1a;在之前&#xff0c;我们对类和对象的上篇进行了讲解&#xff0c;今天我们我将给大家带来的是类和对象中篇的学习&#xff0c;继续深入探讨【C】中类和对象的相关知识&#xff01;&#xff01;&#xff01; 目录 1. 类的6个默认成员函数 2. 构造函数 2.1概念介…

yolov8命令行运行参数详解

序言 整理来自yolov8官方文档常用的一些命令行参数&#xff0c;官方文档YOLOv8 Docs yolov8命令行的统一运行格式为&#xff1a; yolo TASK MODE ARGS其中主要是三部分传参&#xff1a; TASK(可选) 是[detect、segment、classification]中的一个。如果没有显式传递&#xf…

预防ddos攻击选择互联网服务提供商还是专业的ddos防护服务商

随着越来越多的企业进行互联网转型&#xff0c;DDoS (分布式拒绝服务) 攻击也活跃许多。但是大部分企业都依靠他们的互联网服务提供商 (ISP) 来缓解 DDoS 攻击&#xff0c;因为这项服务通常作为 ISP 现有带宽产品的相对低成本的附加服务。黑客非常了解这一点&#xff0c;因此他…

SpringBoot项目切面编程

SpringBoot项目切面编程什么是切面专业术语解释&#xff1a;通俗解释使用Aspect进行切面编程注解说明使用过程Demo什么是切面 专业术语解释&#xff1a; 在软件业&#xff0c;AOP为Aspect Oriented Programming的缩写&#xff0c;意为&#xff1a;面向切面编程&#xff0c;通…

极智AI | 百度推出文心一言,对标ChatGPT功力几成

欢迎关注我,获取我的更多经验分享,极智传送《极智AI | 百度推出文心一言,对标 ChatGPT 功力几成》 大家好,我是极智视界,本文介绍一下 百度今日推出文心一言,对标ChatGPT功力几成。 邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码下载,链接:https…

【Matlab算法】粒子群算法求解一维线性函数问题(附MATLAB代码)

MATLAB求解一维线性函数问题前言正文函数实现可视化处理可视化结果前言 一维线性函数&#xff0c;也称为一次函数&#xff0c;是指只有一个自变量xxx的函数&#xff0c;且函数表达式可以写成yaxbyaxbyaxb的形式&#xff0c;其中aaa和bbb是常数。具体来说&#xff0c;aaa称为斜…

ChatGPT大解密:带您探讨机器学习背后的秘密、利用与发展

一、什么是机器学习&#xff1f;二、ChatGPT 的运作原理三、ChatGPT 生活利用1、自然语言处理2、翻译3、自动回复四、ChatGPT vs 其他相关技术五、ChatGPT 的未来1、未来发展2、职业取代3、客服人员4、翻译人员5、语言学家6、机遇与挑战六、结语这篇文章&#xff0c;将带着各位…

数据结构—二叉树链式结构的实现

目录 0、前言 1、二叉树链式结构的创建 2、二叉树的遍历 3、 前序、中序以及后序遍历 4、 前序、中序以及后序遍历的实现——双路递归 分治思想 求叶子节点数量&#xff0c;分治思想&#xff1a; 分治思想 求第k层节点个数&#xff1a; ​编辑 分治思想 求二叉树的深…

QT Plugin 插件开发

插件是一种&#xff08;遵循一定规范的应用程序接口编写出来的&#xff09;程序&#xff0c;定位于开发实现应用软件平台不具备的功能的程序。 插件必须依赖于应用程序才能发挥自身功能&#xff0c;仅靠插件是无法正常运行的&#xff1b;相反地&#xff0c;应用程序并不…
最新文章