链表(JS实现)

news2024/12/23 16:38:55

📝个人主页:爱吃炫迈
💌系列专栏:数据结构与算法
🧑‍💻座右铭:道阻且长,行则将至💗

文章目录

  • 链表
  • 链表的分类
  • 创建链表
    • LinkedList类的骨架
  • 实现链表的方法
    • push尾部添加元素
    • insert任意处添加元素
    • getElementAt查找元素位置
    • removeAt从指定位置移除元素
    • indexOf查找元素位置
    • remove删除指定元素
    • isEmpty判断链表是否为空
    • toString 将链表转化为字符串
    • size返回链表长度
    • 测试
  • 💞总结💞


链表

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

线性表的顺序存储结构缺点是每一次插入和删除元素,大量元素的移动会导致时间效率低下。为了改进顺序存储结构的缺点,引入链式存储结构,即为链表。

链式存储结构的特点是用一组任意的存储单元来存储线性表中的数据元素。这样在插入和删除元素时,可以通过直接修改指针完成操作,时间效率大大提高。但因为链式存储结构的存储单元不连续,所以需要通过指针来访问它的后续元素。

为了表示每个数据元素与其直接后继数据元素之间的逻辑关系,我们需要存出一个其直接后继的存储位置。我们把存储数据元素信息的域成为数据域,把存储后继位置的域称为指针域,这两部分构成一个节点

n个节点链接成一个链表,即为线性表的链式存储结构。因为每个节点只有一个指针域,所以又将这样的链表称为单链表。


链表的分类

请添加图片描述

线性表可以分为顺序表和链表 :

顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构。


顺序表和链表有以下几点区别

  • 空间开辟方式:顺序表在存储数据之前开辟足够空间,后期无法改变大小(动态数组除外),链表一次只开辟存储一个节点的内存空间。一次开辟大量空间比多次开辟小量空间性能好。
  • 空间利用率:链表每次申请一个节点的空间,且位置随机。这种申请存储空间的方式会有很多空间碎片,一定程度上造成了空间浪费。此外,链表每个节点至少需携带一个指针,增加了空间占用。因此,顺序表空间利用率比链表高。
  • 时间复杂度:顺序表可以使用下标直接访问元素,它的时间复杂度为O(1),链表需从头开始,依次遍历,时间复杂度为O(n)。顺序表中插入、删除、移动元素,可能涉及大量元素的整体移动,时间复杂度为O(n),链表插入、删除、移动元素,只需改变指针指向,时间复杂度为O(1)

链表可以分为以下几类

  • 单向链表:是链表中最简单的,它包含两个域,一个信息域和一个指针域,指针指向链表中的下一个节点,最后一个节点的指针指向一个空值。

请添加图片描述

  • 静态单向链表:用数组描述的单向链表,称为静态单向链表。它的内存地址是连续的,需预先分配大小。
  • 动态单向链表:用申请内存的函数动态申请内存,链表长度没有限制,节点内存地址不是连续的,需通过指针来顺序访问。
  • 双向链表:每个节点有两个连接,一个指向前一节点,另一个指向后一节点。首、尾节点对应的前、后连接指向空值或空列表。

请添加图片描述

  • 循环链表:首节点、尾节点连接在一起。循环链表第一个节点之前就是最后一个节点;反之亦然。

请添加图片描述

  • 单向循环链表:它的最后一个节点指针不是 NULL,而改为指向头节点,从而整个链表形成一个环。
  • 双向循环链表:由单向循环链表可以推断出双向循环链表,它的头节点还会指向尾节点。

这篇文章只介绍单向链表。


创建链表

LinkedList类的骨架

// 判断两个元素是否相等
function defaultEquals(a, b) {
  return a === b
}

// 创建元素节点
class Node {
  constructor(element) {
    this.element = element;
    this.next = undefined;
  }
}

// 定义LinkedList类,表示链表
class LinkedList {
  // 可以自定义传入比较元素相等的方法,如果没有使用defaultEquals
  constructor(equalsFn = defaultEquals) {
      this.count = 0; // 存储链表数量
      this.head = undefined; // 第一个元素
      this.equalsFn = equalsFn; // 判断两个元素是否相等
  }
  push(element) {} // 向链表尾部添加一个新元素
  insert(element,position) {} // 向链表特定位置插入一个新元素
  getElementAt(index) {} // 返回链表特定位置的元素
  remove(element) {} // 从链表中移除一个元素
  indexOf() {} // 返回元素在链表中的索引,没有则返回-1
  removeAt(position) {} // 从链表特定位置移除一个元素
  isEmpty() {} // 判断链表是否为空
  size() {} // 返回链表包含的元素个数
  toString() {} // 返回整个链表的字符串
 }

实现链表的方法

  • push尾部添加元素
  • insert任意处添加元素
  • getElementAt查找元素位置
  • removeAt从指定位置移除元素
  • indexOf查找元素位置
  • remove删除指定元素
  • isEmpty判断链表是否为空
  • toString 将链表转化为字符串
  • size返回链表长度

push尾部添加元素

  • 新增元素分为两种情况,链表为空和链表不为空。
  • 链表为空时,新增的元素作为链表头(直接赋值);
  • 链表不为空时,从链表尾添加,即原链表尾的指针next指向新增元素。
push(element) {
    const node = new Node(element);// 创建新节点
    let current;
    // 头部为空
    if (this.head == null) {
        this.head = node;
        // 头部不为空
    } else {
        current = this.head; // 当前元素
        while(current.next != null) { // 获取最后一项
            current = current.next;
        }
        // 将next赋值为新元素,建立链接
        current.next = node;
    }
    this.count++;
}

insert任意处添加元素

  • 根据下标找到目标节点,从目标节点之前插入新元素,
  • 插入元素分为两种情况,头部插入和非头部插入。
  • 头部插入(下标等于0),即插入的元素作为链表头;
  • 非头部插入,找到目标节点和目标节点的前一个节点(我们称之为前节点),使前节点的指针指向插入节点,而插入节点的指针指向目标节点,完成插入。
  • 如下图是非头部插入示意图,node2为目标节点,node1为前节点,node3为插入节点,使node1的指针指向node3,而node3的指针指向node2,即完成了插入操作。

请添加图片描述

insert(element, index) {
    if (index >= 0 && index <= this.count) {
        const node = new Node(element);// 定义一个新结点
        if (index === 0) { // 链表头添加
            const current = this.head;// 先把原链表头赋值给current
            node.next = current;// 链表头的指针指向原链表头,完成插入
            this.head = node;// 插入的新节点作为链表头
        } else { //非链表头添加
            const previous = this.getElementAt(index - 1);
            const current = previous.next;
            node.next = current; // 插入项的指针指向目标项
            previous.next = node;// 目标项的前一项的指针指向插入项
        }
        this.count ++; // 长度加1
        return true;
    }
    return false;
}

getElementAt查找元素位置

getElementAt(index) {
    if (index >= 0 && index < this.count) {
        let node = this.head;//从头部开始查找
        for (let i = 0; i < index && node != null; i++) {// 遍历链表
            node = node.next;
        }
        return node;
    }
    return undefined;
}

removeAt从指定位置移除元素

  • 根据下标移除元素也分为两种情况,移除链表头和移除非链表头节点。
  • 移除链表头节点(下标等于0),即使原链表头的指针指向的节点作为新链表头;
  • 移除非链表头节点,根据下标找到需要移除的目标节点和它的前一个节点,使前结点的指针 指向 目标节点的指针所指向的节点,即完成了移除目标节点。
  • 如下图是移除非链表头节点示意图,node1为目标节点,head为前节点,node2为node1指针指向的节点,使head的指针指向node2,即完成了移除node1节点的操作。

请添加图片描述

// 从链表中移除元素,并返回移除项
removeAt(index) {
    // 检查越界值
    if (index >= 0 && index < this.count) {
        let current = this.head;
        // 移除链表头
        if (index === 0) {
            this.head = current.next;// 使原表头的指针变成新表头
        } else {// 移除非链表头
            const previous = this.getElementAt(index - 1);
            current = previous.next;
            previous.next = current.next;// 跳过当前项,使前一项的指针指向当前项的指针
        }
        this.count--;
        return current.element;
    }
    return undefined;
}

indexOf查找元素位置

从头部开始查找,如果当前查找节点等于目标节点就返回对应的下标,否则继续往后查找;若链表循环结束了还没找到,则返回-1(不存在)。

// 查找元素位置
indexOf(element) {
    let current = this.head;// 从头部开始查找
    for (let i = 0; i < this.count && current != null; i++) {
        if (this.equalsFn(element, current.element)) {// 如果当前节点的值等于目标项
            return i;// 返回对应下标
        }
        current = current.next;// 否则继续往后查找
    }
    return -1;
}

remove删除指定元素

  • 调用indexOf(element)方法找到目标项的下标;

  • 调用removeAt(position)方法根据目标项的下标移除目标项

// 从链表中移除元素
remove(element) {
    const index = this.indexOf(element);
    return this.removeAt(index);
}

isEmpty判断链表是否为空

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

toString 将链表转化为字符串

toString() {
  if (this.head == null) {
    return '';
  }
  let objString = `${this.head.element}`;
  let current = this.head.next;
  for (let i = 1; i < this.size() && current != null; i ++) {
    objString = `${objString},${current.element}`;
    current = current.next;
  }
  return objString;
}

size返回链表长度

size() {
  return this.count;
}

测试

const list = new LinkedList();
console.log(list.isEmpty()); //true
list.push(12);
list.push(11);
list.push(10);
list.push(9);
console.log(list); //12 11 10 9
console.log(list.size());//4
console.log(list.isEmpty()); //falae
console.log(list.indexOf(12)); //0
console.log(list.getElementAt(0)); //12
console.log(list.toString()); //12,11,10,9
// insert()添加元素
list.insert(3, 3);
console.log(list);//12 11 10 3 9
// removeAt():移除指定位置的元素
list.removeAt(2);
console.log(list); //12 11 9
// remove():删除元素
list.remove(11)
console.log(list); //12 10 9

💞总结💞

希望我的文章能对你学习栈的知识点有所帮助!

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

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

相关文章

chatgpt智能提效职场办公-ppt怎么蒙层

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 在 PowerPoint 中添加蒙版图层&#xff0c;可以在幻灯片中创建一个半透明的矩形或形状&#xff0c;并在其上方添加或放置其他对象。 下…

FPGA终于可以愉快地写代码了!Vivado和Visual Studio Code黄金搭档

如果你是一位FPGA开发者&#xff0c;那么你一定会对VIvado这款软件非常熟悉。但是&#xff0c;对于vivado兼容的第三方编辑器软件&#xff0c;你知道Visual Studio Code吗&#xff1f;这是个非常不错的选择&#xff0c;Visual Studio Code搭配众多插件&#xff0c;能让你FPGA开…

【SpringBoot】一:SpringBoot的基础(下)

文章目录 1.外部化的配置1.1 配置文件基础1.1.1 配置文件格式1.1.2 application文件1.1.3 application.properties1.1.4 application.yml1.1.5 environment1.1.6 组织多文件1.1.7多环境配置 1.2 绑定Bean1.2.1 简单的属性绑定1.2.2 嵌套Bean1.2.3 扫描注解1.2.4 处理第三方库对…

【移动端网页布局】移动端网页布局基础概念 ② ( 视口 | 布局视口 | 视觉视口 | 理想视口 )

文章目录 一、视口1、布局视口 ( 网页大小 | 网页大小 > 设备大小 )2、视觉视口 ( 设备大小 | 网页大小 > 设备大小 )3、理想视口 ( 网页大小 设备大小 ) 一、视口 浏览器 显示 网页页面内容 的 屏幕区域 被称为 " 视口 " ; 视口分为以下几个大类 : 布局视口…

项目协同中的git

在远程代码仓库&#xff08;云效&#xff0c;gitee&#xff0c;github&#xff0c;Coding等&#xff09;新建一个代码库&#xff0c; 我使用的云效 新建一个develop分支&#xff0c;后续所有人的提交代码都合并到develop分支上面&#xff0c;一般develop分支是用来开发用的&…

尚融宝22-提交借款申请

目录 一、需求介绍 二、图片上传 &#xff08;一&#xff09;前端页面 &#xff08;二&#xff09;实现图片上传 三、数据字典展示 &#xff08;一&#xff09;后端 &#xff08;二&#xff09;前端 四、表单信息提交 &#xff08;一&#xff09;后端 1、VO对象&…

嵌入式工程师如何快速的阅读datasheet的方法

目录 ▎从项目角度来看datasheet ▎各取所需 ▎最后 Datasheet&#xff08;数据手册&#xff09;的快速阅读能力&#xff0c;是每个工程师都应该具备的基本素养。 无论是项目开始阶段的选型还是后续的软硬件设计&#xff0c;到后期的项目调试&#xff0c;经常有工程师对着英…

06-Node.js—模块化

目录 1、介绍1.1 什么是模块化与模块 ?1.2 什么是模块化项目 ?1.3 模块化好处 2、模块暴露数据2.1 模块初体验2.2 暴露数据2.2.1 module.exports value2.2.2 exports.name value 3、导入&#xff08;引入&#xff09;模块4、导入模块的基本流程5、CommonJS 规范参考 1、介绍…

使用RabbitMQ的手动接收模式:消息第二次入队Failed to declare queue

问题&#xff1a;在rabbitMQ测试使用手动接收模式时发生 Failed to declare queue错误 : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code406, reply-textPRECONDITION_FAILED - unknown delivery tag 1, class-id60, method-id80…

C++ 命名空间、域、缺省参数、函数重载、引用、auto、内联函数的知识点+完整思维导图+基本练习题+深入细节+通俗易懂建议收藏

绪论 从本章开始我们正式进入到C的内容&#xff0c;对此如果没有学习过C语言的建议先将C语言系统的学习一遍后再来&#xff08;已经更新完在专栏就能看到&#xff09;。 话不多说安全带系好&#xff0c;发车啦&#xff08;建议电脑观看&#xff09;。 附&#xff1a;红色&#…

Linux运维基础

一.vim编辑器 1.编辑器介绍 vi/vim是visual interface的简称,是Linux中最经典的文本编辑器&#xff0c;同图形化界面中的文本编辑器一样&#xff0c;vi是命令行下对文本文件进行编辑的绝佳选择&#xff0c;粗暴理解相当于windows下的记事本。 vim是vi的加强版本,兼容vi的所有…

java版UWB人员定位系统源码,提供位置实时显示、历史轨迹回放、电子围栏、行为分析、智能巡检等功能

运用UWB定位技术开发的人员定位系统源码 文末获取联系&#xff01; 本套系统运用UWB定位技术&#xff0c;开发的高精度人员定位系统&#xff0c;通过独特的射频处理&#xff0c;配合先进的位置算法&#xff0c;可以有效计算复杂环境下的人员与物品的活动信息。 提供位置实时显…

SLAM论文速递【SLAM—— DynaSLAM:动态场景中的跟踪、建图和修复—4.19(1)

论文信息 题目&#xff1a; DynaSLAM:Tracking,Mapping and Inpainting in Dynamic Scenes DynaSLAM:动态场景中的跟踪、映射和修复论文地址&#xff1a; https://arxiv.org/pdf/1806.05620.pdf发表期刊&#xff1a; IEEE Robotics and Automation Letters ( Volume: 3, Issu…

RPC一文精通

基础&#xff1a; http是基于应用层协议&#xff0c;对请求和响应进行规范包装,一次http请求就会进行一次tcp连接和断开连接&#xff0c;属于短链接 udp是异步响应&#xff0c;无需建立连接&#xff0c;就可以发送封装的IP数据包 tcp是基于传输层协议&#xff0c;并规范了三…

Python单向循环链表操作

目录 一、单向循环链表 单向循环链表图 二、单向循环链表的操作 1、判断链表是否为空 2&#xff0c;链表长度 3&#xff0c;遍历整个链表 4&#xff0c;在链表头部添加元素 5、链表尾部添加元素 6&#xff0c;在指定位置插入元素 7&#xff0c;修改指定位置的元素 8&a…

JavaSE 和 Java EE 分别是什么

Java 作为最流行的编程语言受到了许多人的喜爱&#xff0c;其在编程中的地位自不必多说。 对于许多才刚刚入门 Java 的朋友来讲&#xff0c;常常会产生这样的困惑&#xff0c;JavaEE是什么&#xff1f;JavaSE又是什么&#xff1f; Java SE Java SE 是 Java Platform, Standa…

Liunx下进程间通信

文章目录 前言1.进程间通信相关介绍2.管道1.匿名管道2.管道的原理3.通过代码来演示匿名管道4.命名管道5.命名管道的原理6.命名管道代码演示 3.System V共享内存1.共享内存原理2.相关系统接口的介绍与共享内存的代码演示3.共享内存的一些特性 4.system V消息队列与system V信号量…

依赖注入方式

Spring中有哪些注入方式? 我们先来思考 向一个类中传递数据的方式有几种? 普通方法(set方法)构造方法 依赖注入描述了在容器中建立bean与bean之间的依赖关系的过程&#xff0c;如果bean运行需要的是数字或 字符串呢? 引用类型简单类型(基本数据类型与String) Spring就…

Primo Ramdisk内存盘工具软件

简介 Primo Ramdisk 软件的主要功能是通过独特的软件算法将物理内存模拟成一个超快速的硬盘&#xff0c;在这个虚拟硬盘上的读写操作均在内存中完成。由于物理内存的访问速度远远超过物理硬盘&#xff0c;因此虚拟硬盘具有非常高的数据读写速度&#xff0c;从而突破系统IO瓶颈&…

“SCSA-T学习导图+”系列:下一代防火墙

本期引言&#xff1a; 近年来&#xff0c;随着数字化业务带给我们高效和便捷的同时&#xff0c;信息暴露面的增加、网络边界的模糊化以及黑客攻击的产业化&#xff0c;使得网络安全事件相较以往成指数级增加。传统防火墙基于五元组的方式对进出网络的数据流量进行访问控制&…