作为前端,你是否了解链表这种数据结构?

news2024/10/6 22:29:55

在面试中只要被问到React Hooks就常被问到为什么Hooks不能在循环和条件判断嵌套函数当中使用;相信很多人都知道标准答案,【因为声明的Hooks保存在链表当中】,但是你真的知道链表吗?

什么是链表

我们先看来看一个简单的单向链表结构

如上图所示我们可以分析出,链表是由多个 node(节点) 组成的,而node(节点) 指向(保存)不同的内存空间,每个node(节点)item(数据域)next(指针域) (双向链表还包括prev指针域)构成,其中item(数据域) 用于存储数据,next(指针域) 指向下一个节点从而形成一个存储数据的线性链路

总结:链表是一个用于存储数据的无序线性数据结构

而通过指针域指向链路的差异我们大致可以分为:

1.单向链表
2.双向链表
3.环形链表

链表与数组的比较

不知道链表这种数据结构能否让你想起数组,这两种都是用于存储数据的线性数据结构,而不同的是链表是一种无序线性数据结构,而数组是一种有序线性数据结构,我们都知道数组是一种引用类型数据结构,当我们创建一个数组的时候会在内存种开辟一串连续的内存空间用于存储,数组就是依靠这串连续的内存空间来维持线性链路,而链表则是有一连串无序的内存保存node(节点) 通过node(节点) 的指针域指向下一个节点来维持线性链路

链表有什么作用?

假设现在有一百条客户的数据你需要对这一百条数据进行增、删、插入你会怎么做?

创建一个数组将100条数据存入数组当中,通过数组的push,splice,findIndex,indexOf等方法对数组进行操作,对于少量数据答案是显而易见的,我们直接通过数组就能解决;但是如果现在有一百万条数据让你操作呢?我们已经提到数组是通过连续内存地址来维持线性链路的一种有序线性结构,如果你在头部插入一条数据的话则后面的一系列元素都要向后移动一位,一百万条数据则要移动一百万次,如果你要删除第一万个元素则后面的九十九万个元素要向前移动一个位置,如果要将第一条数据替换为最后一条数据则要先删除再插入先将第一条数据与最后一条数据取出其余所有数据要向前移动一个位(除头部数据与尾部数据),然后再替换插入,所有数据再向后移动一位;在数据量庞大的情况下这绝对不是一个明智的方案,因为时间维度的不允许;但是如果换成链表你只需要操作node(节点) 指针域的指向便可以完成以上工作;

链表的优缺点

优点:相较于数组,链表操作更加的灵活,不受存储空间的限制;

缺点:

  • 链表不能通过下标获取值,每次要获取链表当中的node(节点) 都需要经过遍历
  • 对于存储基本类型的数据结构因为需要通过指针域的指向则需要多分配一块内存进行存储(双向链表则乘以2)

通过JS简单实现一个单向链表

而对于链表操作我们大致可以分为

1.新增
2.插入
3.删除
4.查看
5.修改

我们以单项链表为例依次实现他们

创建Node辅助类

我们已经知道链表的大致概念也就是链表是一种以多个node(节点) 通过指针域连接的无序线性数据结构,因此首先我们需要创建辅助类Node用于创建node(节点)

//辅助类Node用于创建保存在链表中的node
class Node {constructor (item) {//数据域,用于保存数据this.item = item//指针域,用于指向下一个节点this.next = null}
} 

而在链表中始终有一个head属性,这个属性是链表的开端,用于存放整个链表的线性链路;我们还需要一个size用于保存链表的长度,用于遍历链表;

//用于创建一个链表
class Linked{constructor () {//size属性用于保存链表的长度用于遍历this.size = 0//用于存放线性链路this.head = null}
} 

至此我们已经完成了创建一个链表的准备工作;那么让我们看看链表的基本操作方法是如何实现的

单向链表新增操作

对于单向链表新增如果当前链表为空我们需要将链表的head属性直接指向创建的node(节点),如果链表不为空则我们需要将最末端的node(节点)next(指针域) 指向新创建的 node(节点)

class Linked {constructor () {this.size = 0this.head = null}//新增node方法add (item) {//创建nodelet node = new Node (item)//this.head === null则代表链表为空需要将新head指向新创建的nodeif (this.head === null) {this.head = node} else {//查找需要创建的节点的上一个节点(最末端节点)let prevNode = this.getNode (this.size - 1)//将末端节点的next指向新创建的nodeprevNode.next = node}//新增成功则size+1this.size++}//节点查找方法传入index类似于数组下标用于标记查找getNode (index) {//如果index < 0 || index >= this.size则说明下标越界需要在此限制if (index < 0 || index >= this.size) {throw new Error ('out range')}//获取第一个节点,从第一个节点进行遍历let current = this.head;for (let i = 0; i < index; i++) {//依次将当前节点指向下一个节点,直到获取最后一个节点current = current.next}return current}
} 

单向链表插入操作

对于单向链表插入操作如果需要插入的位置为下标为0的位置(头部),我们需要将需要插入的node(节点)next(指向域),指向未改变之前的第一个node(节点),也就是head,如果是中间追加则我们需要 将插入node(节点) 的指向域指向插入下标的上一个节点的指向域(插入节点的下一个节点),并将插入node(节点) 的上一个节点的指向域,指向当前节点

class Linked {constructor () {this.size = 0this.head = null}//追加插入//position为插入位置下标,item为需要保存到节点的元素insert (position, item) {//下标值越位判断if (position < 0 || position > this.size) {throw new Error ('Position out range');}//创建新节点let node = new Node (item)//头部追加//如果插入下标为0则直接将head指向新创建的节点if (position === 0) {node.next = this.head;this.head = node} else {//中间追加//获取追加节点的上一个节点let prevNode = this.getNode (position - 1)//将插入下标的指向域指向插入下标的上一个节点的指向指向域(下一个节点)node.next = prevNode.next//将插入下标的上一个节点的指向域,指向当前节点prevNode.next = node}//插入成功,长度加一this.size++}getNode (index) {if (index < 0 || index >= this.size) {throw new Error ('out range')}let current = this.head;for (let i = 0; i < index; i++) {current = current.next}return current}
} 

单向链表删除操作

对于单向链表的删除操作,如果删除元素为链表的顶端元素则需要将head指向被删除元素的指针域(下一个元素),如果不是第一个元素则我们需要将上一个元素的指针域指向被删除元素的next(指针域)(下一个元素)

class Linked {constructor () {this.size = 0this.head = null}delete (position) {//删除下标合法判断if (position < 0 || position >= this.size) {throw new Error ('position out range')}//获取当前链表(head)let linkList = this.head//如果删除的是链表的第一个元素则将head指向第一个元素的指针域(下一个元素)if (position === 0) {this.head = linkList.next;} else {//中间删除//获取删除元素的上一个节点let prevNode = this.getNode (position - 1)//将链表指向被删除元素上一个节点linkList = prevNode.next//将链表的的上一个节点指向被删除元素的下一个节点prevNode.next = linkList.next}//长度减一this.size--}getNode (index) {if (index < 0 || index >= this.size) {throw new Error ('out range')}let current = this.head;for (let i = 0; i < index; i++) {current = current.next}return current}

 } 

单向链表查找操作

对于查找操作我们需要对链表进行遍历比对获取下标

class Linked {constructor () {this.size = 0this.head = null}//查找指定元素下标findIndex (item) {//获取当前链表let current= this.head//进行遍历操作依次比对获取查找元素下标for(let i=0;i<this.size;i++){if (current.item === item) {//如果current.item === item则说明该元素为需要查找的元素,返回下标return i}//使聊表指向他的下一个元素,使循环可以继续 current = current.next}return null}getNode (index) {if (index < 0 || index >= this.size) {throw new Error ('out range')}let current = this.head;for (let i = 0; i < index; i++) {current = current.next}return current}
} 

单向链表修改操作

对于单向链表的修改操作我们只需用下标获取需要修改的元素,并对其item重新进行赋值即可;

class Linked {constructor () {this.size = 0this.head = null}getNode (index) {if (index < 0 || index >= this.size) {throw new Error ('out range')}let current = this.head;for (let i = 0; i < index; i++) {current = current.next}return current}//修改操作//position为需要修改元素的下标,item为修改的值update(position,item){let current = this.getNode(position)current.item = item}
} 

单向链表类方法整合

class Node {constructor (item) {this.item = itemthis.next = null}
}

class Linked {constructor () {this.size = 0this.head = null}add (item) {let node = new Node (item)if (this.head === null) {this.head = node} else {let current = this.getNode (this.size - 1)current.next = node}this.size++}//追加插入insert (position, item) {//下标值越位判断if (position < 0 || position > this.size) {throw new Error ('Position out range');}//创建新节点let node = new Node (item)//头部追加//如果插入下标为0则直接将head指向新创建的节点if (position === 0) {node.next = this.head;this.head = node} else {//中间追加let prevNode = this.getNode (position - 1)//将插入下标的指向域指向插入下标的上一个节点的指向指向域(下一个节点)node.next = prevNode.next//将插入下标的上一个节点的指向域,指向当前节点prevNode.next = node}//插入成功,长度加一this.size++}delete (position) {//删除下标合法判断if (position < 0 || position >= this.size) {throw new Error ('position out range')}//获取当前链表(head)let linkList = this.head//如果删除的是链表的第一个元素则将head指向第一个元素的指针域(下一个元素)if (position === 0) {this.head = linkList.next;} else {//中间删除//获取删除元素的上一个节点let prevNode = this.getNode (position - 1)//将链表指向被删除元素上一个节点linkList = prevNode.next//将链表的的上一个节点指向被删除元素的下一个节点prevNode.next = linkList.next}//长度减一this.size--}//查找指定元素下标findIndex (item) {//获取当前链表let current= this.head//进行遍历操作依次比对获取查找元素下标for(let i=0;i<this.size;i++){if (current.item === item) {//如果current.item === item则说明该元素为需要查找的元素,返回下标return i}//使聊表指向他的下一个元素,使循环可以继续 current = current.next}return null}getNode (index) {if (index < 0 || index >= this.size) {throw new Error ('out range')}let current = this.head;for (let i = 0; i < index; i++) {current = current.next}return current}//修改操作//position为需要修改元素的下标,item为修改的值update(position,item){let current = this.getNode(position)current.item = item}
} 

写在最后

对于链表的使用感受,我觉得我们不能将链表看成一个整体的数据结构,而是要将它单元化,通过指针域来灵活的使用它。其实我更加倾向于将链表理解成一种设计思路,我们也没必要将每个指针域命名为next,我们可以通过指针域的不同来构建千变万化的数据结构,它也可以拥有不止一个指针域,如果我们添加一个prev指针指向上一个节点那么这个链表就是一个双向链表,如果链表的最底层next指向的不是null而是当前链表中任意一个节点,那么它就是一个环形链表;当然我们也可以使一个节点拥有left和right两个指针域,指向不同的节点,那么它就是一个二叉树,甚至我们可以用链表的指针域概念来实现一个优先队列,虽然在前端开发中链表的操作非常少,但是在阅读源码当中我不止一次的看到链表这种数据结构。说白了,这是一个无用的知识,因为你在开发当中基本上用不到,但是链表的指针域概念却能提升你的思维,让你对数据结构有更广的思考;本来我是想再讲讲双向链表与环形链表的,但是我实在不知道如何用文字表达用快慢指针来判断环形链表中是否存在一个环,因此便没有继续;

最后希望正在找工作的朋友都能找到满意的工作,已经拥有满意工作的朋友都能顺顺利利!也希望接下来行情可以慢慢好起来!

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

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

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

相关文章

华为珍藏版:SpringBoot全优笔记,面面俱到太全了

前言 作为开发人员&#xff0c;对于Spring全家桶肯定是不陌生的&#xff0c;而来自于Spring大家族的Spring Boot&#xff0c;作为Spring团队提供的流行框架&#xff0c;它的存在解决的Spring框架使用较为繁琐的问题&#xff0c;所以掌握SpringBoot是精通Spring必不可少的一个过…

[附源码]Python计算机毕业设计Django血库管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

[附源码]计算机毕业设计Node.js-Bigbaby美食网站(程序+LW)

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

《Go语言并发之道》读书笔记

《Go语言并发之道》第一章&#xff1a; 并发概述第二章&#xff1a;对你的代码建模&#xff1a;通信顺序进程第三章&#xff1a;GO语言并发组件由于不怎么熟悉GO&#xff0c;只做简单的摘录&#xff0c;敲敲示例代码 鸭子类型&#xff1a;当看到一只鸟走起来像鸭子、游泳起来像…

微服务框架 SpringCloud微服务架构 多级缓存 49 缓存同步 49.3 监听Canal

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 多级缓存 文章目录微服务框架多级缓存49 缓存同步49.3 监听Canal49.3.1 Canal 客户端49 缓存同步 49.3 监听Canal 49.3.1 Canal 客户端 C…

深耕无线通讯细分领域 可信华成产品受市场青睐

深圳市可信华成通信科技有限公司&#xff08;以下简称可信华成&#xff09;&#xff0c;成立于2010年&#xff0c;是一家在无线通信领域中崛起的集研发、智能制造、销售为一体的国家高新技术企业&#xff0c;深圳市专精特新企业&#xff1b; 注册资金2200万元&#xff0c;员工8…

【图像压缩】余弦变换及霍夫曼编码jpeg压缩和解压【含Matlab源码 2086期】

⛄一、DCT图像无损压缩简介 1 图像压缩 图像压缩按照压缩过程中是否有信息的损失以及解压后与原始图像是否有误差可以分为无损压缩和有损压缩两大类。无损压缩是指不损失图像质量的压缩&#xff0c;它是对文件的存储方式进行优化&#xff0c;采用某种算法表示重复的数据信息&a…

网络安全——【收藏】网络设备安全加固规范

一、Cisco网络设备安全基线规范 本建议用于Cisco路由器和基于Cisco IOS的交换机及其三层处理模块&#xff0c;其软件版本为CISCO IOS 12.0及以上版本。加固前应该先备份系统配置文件。 01 账号管理、认证授权 1.1.本机认证和授权 初始模式下&#xff0c;设备内一般建有没有…

Linux——用户、组的管理以及文件的权限设置

一、用户和组 Linux系统中的用户唯一的标识码为用户ID&#xff0c;即UID&#xff0c;每个用户至少属于一个组&#xff0c;即为用户分组。用户分组存在唯一的标识码&#xff0c;为GID。不同的用户拥有不同的权限。 1&#xff0e;认识用户账号文件/etc/passwd和用户影子文件/et…

Java项目:SSM汽车租车管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目包含管理员、普通用户两种角色&#xff1b; 管理员主要功能包括&#xff1a; 后台首页、停车位信息管理、车辆求租信息审核、车辆出租信息…

热门技术中的应用:容器技术中的网络-第31讲-容器网络之Calico:为高效说出善意的谎言

上一节我们讲了Flannel如何解决容器跨主机互通的问题,这个解决方式其实和虚拟机的网络互通模式是差不多的,都是通过隧道。但是Flannel有一个非常好的模式,就是给不同的物理机设置不同网段,这一点和虚拟机的Overlay的模式完全不一样。 在虚拟机的场景下,整个网段在所有的物…

操作系统(3)银行家算法模拟实现

参考博客&#xff1a;银行家算法详解&#xff08;C语言&#xff09;_Sparky*的博客-CSDN博客_银行家问题c语言 1. 效果展示 2. 程序流程图 3. 数据结构设计 /**定义数据结构*/ vector<vector<int>> Max;// 最大需求矩阵 vector<vector<int>> Allocat…

小白如何入门Python爬虫?这是我见过最详细的入门教学

本文针对初学者&#xff0c;我会用最简单的案例告诉你如何入门python爬虫&#xff01; 想要入门Python 爬虫首先需要解决四个问题 熟悉python编程 了解HTML 了解网络爬虫的基本原理 学习使用python爬虫库 01了解什么是爬虫&#xff0c;它的基本流程是什么&#xff1f; 网络…

IDEA 2022 之 Lombok 使用 教程

文章目录**1.Lombok是什么****1.1 Lombok 是什么&#xff1f;****Lombok 引入**2、POM 中引入依赖3、IDE 中安装插件**4. Lombok 使用****4.1 Lombok 使用注意**5.代码案例&#xff1a;**Lombok 原理**6. 常用注解结语1.Lombok是什么 ​ Lombok是使用java编写的一款开源类库。…

【Redis】Redis缓存穿透、缓存雪崩、缓存击穿详解与解决办法(Redis专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;专注于研究 Java/ Liunx内核/ C及汇编/计算机底层原理/源码&#xff0c;就职于大型金融公司后端高级工程师&#xff0c;擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。 &#x1…

Java项目:springboot大学生实习管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本系统的用户可以分为三种&#xff1a;管理员、教师、学生。三种角色登录后会有不同菜单界面&#xff1b; 管理员主要功能&#xff1a; 信息管…

graalvm 拯救生命,速速入手

graalvm 拯救生命&#xff0c;速速入手 标题很夸张&#xff0c;graalvm怎么就拯救生命了&#xff1f;把一个启动5-6秒的项目加速到3秒启动&#xff0c;不就是在拯救生命&#xff0c;拯救发际线吗&#xff1f; 我在上一篇博客"SpringBoot3.0工程建立"末尾启动了工程…

高级网络应用复习——三层热备生成树速端口OSPF实验(带命令)

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.知识点总结 路由器热备份技术HSRP &#xff08;思科私有 HS…

学web前端开发和学习其他编程语言一样吗?

前言&#xff1a; web前端是编程中门槛较低&#xff0c;较易入门的&#xff0c;对年龄和学历要求也不是特别高&#xff0c;但如果学历过低&#xff0c;年龄比较大&#xff0c;又完全没有基础&#xff0c;会在学习时感到吃力&#xff0c;另外也会因为用人公司对学历和年龄的限制…

电巢:半导体投资锐减库存调整消费者需求疲软,半导体下行周期何时结束?

前言 投行PitchBook的资料显示截止到本月5日&#xff0c;2022 年全球半导体初创企业的风险投资达到 78 亿美元。与去年创纪录的 145 亿美元投资者注入硅公司的资金相比下降了 46%&#xff0c;与 2020年的103 亿美元相比下降了 24%。 高盛&#xff08;Goldman sachs&#xff09;…