iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > html >javascript中链表和数组的详细介绍和使用
  • 316
分享到

javascript中链表和数组的详细介绍和使用

2024-04-02 19:04:59 316人浏览 薄情痞子
摘要

这篇文章主要讲解了“javascript中链表和数组的详细介绍和使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“javascript中链表和数组的详细介绍

这篇文章主要讲解了“javascript链表数组的详细介绍和使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“javascript中链表和数组的详细介绍和使用”吧!

你将收获

  • 链表的概念和应用

  • 原生javascript实现一条单向链表

  • 原生javascript实现一条个双单向链表

  • 链表和数组的对比及优缺点

正文

1. 链表的概念和应用

链表是一种线性表数据结构,由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

以上概念用图表示为以下结构:

javascript中链表和数组的详细介绍和使用

链表是非连续的,所以说从底层存储结构上看,它不需要一整块连续的存储空间,而是通过“指针”将一组零散的数据单元串联起来成为一个整体。链表也有几种不同的类型:单向链表,双向链表,循环链表。上图就是一种单向链表。由其定义不难发现双向链表无非就是每个节点加上了前后节点的指针引用,如下图所示:

javascript中链表和数组的详细介绍和使用

那什么是循环链表呢?循环链表本质上是一种特殊的单向链表,唯一的区别就在于它的尾结点指向了链表的头结点,这样首尾相连,形成了一个环,所以叫做循环链表。如下图所示:

javascript中链表和数组的详细介绍和使用

当然我们还可以扩展出双向循环链表,这里就不一一举例了。总之链表结构在计算机底层语言中应用的比较多,当我们在用高级语言做编程时可能不会察觉,比如我们用javascript敲js的时候,其实我们在深入了解链表之后我们就会发现链表有很多应用场景,比如LRU  缓存淘汰,最近消息推送等。

举个更接地气的,当我们在用PS画图时软件提供了一个动作面板,可以记录用户之前的操作记录,并批量执行动作,或者当我们在使用编辑器时的回退撤销功能等,用链表结构来存储状态信息还是比较方便的。

最近比较火的React hooks  api,其结构也是一个链表型的数据结构,所以学习链表还是非常有帮助的。读到这里可能还是有点懵,接下来我们先用js实现一个链表,这样有助于理解链表的本质,后面笔者会总结一下链表和数组的对比以及优劣势,方便大家对链表有一个更加直观的认识。

2.原生javascript实现一条单向链表

在上面一节介绍的链表结构中大家可能对链表有了初步的认识,因为javascript中没有链表的数据结构,为了模拟链表结构,我们可以通过js面向对象的方式实现一个链表结构及其API,具体设计如下:

javascript中链表和数组的详细介绍和使用

有了以上需求点之后,这个链表才是基本可用的链表,那么我们一步步来实现它吧。

2.1 定义链表结构

为了实现链表以及链表的操作,首先我们需要先定义链表的基本结构,第一步就是定义节点的数据结构。我们知道一个节点会有自己的值以及指向下一个节点的引用,所以可以这样定义节点:

let node = function(el) {       this.el = el;       this.next = null;  }

接下来我们定义一下链表的基本骨架:

// 单向链表, 每一个元素都有一个存储元素自身的节点和一个指向下一个元素引用的节点组成 function linkedList() {   let Node = function(el) {       this.el = el;       this.next = null;   }   let length = 0   let head = null  // 用来存储第一个元素的引用    // 尾部添加元素   this.append = (el) => {};   //插入元素   this.insert = (pos, el) => {};   // 移除指定位置的元素   this.removeAt = (pos) => {};   // 移除指定节点   this.remove = (el) => {};   // 查询节点所在位置   this.indexOf = (el) => {};   // 判断链表是否为空   this.isEmpty = () => {};   // 返回链表长度   this.size = () => {};   // 将链表转化为数组返回   this.toArray = () => {}; }

由以上代码我们可以知道链表的初始长度为0,头部元素为null,接下来我们实现添加节点的功能。

2.2 实现添加节点

追加节点的时候首先需要知道头部节点是否存在,如果不存在直接赋值,存在的话则从头部开始遍历,直到找到下一个节点为空的节点,再赋值,并将链表长度+1,代码如下:

// 尾部添加元素 this.append = (el) => {     let node = new Node(el),         current;     if(!head) {       head = node     }else {       current = head;       while(current.next) {         current = current.next;       }       current.next = node;     }     length++ };

2.3 实现插入节点

实现插入节点逻辑首先我们要考虑边界条件,如果插入的位置在头部或者比尾部位置还大,我们就没必要从头遍历一遍处理了,这样可以提高性能,所以我们可以这样处理:

//插入元素 this.insert = (pos, el) => {     if(pos >=0 && pos <= length) {       let node = new Node(el),           previousNode = null,           current = head,           curIdx = 0;       if(pos === 0) {         node.next = current;         head = node;       }else {         while(curIdx++ < pos) {           previousNode = current;           current = current.next;         }         node.next = current;         previousNode.next = node;         length++;         return true       }     }else {       return false     } };

2.4 根据节点的值查询节点位置

根据节点的值查询节点位置实现起来比较简单,我们只要从头开始遍历,然后找到对应的值之后记录一下索引即可:

// 查询节点所在位置 this.indexOf = (el) => {     let idx = -1,         curIdx = -1,         current = head;     while(current) {       idx++       if(current.el === el) {         curIdx = idx         break;       }       current = current.next;     }     return curIdx };

这里我们之所以要用idx和curIdx两个变量来处理,是因为如果用户传入的值不在链表里,那么idx的值就会有问题,所以用curIdx来保证准确性。

2.5 移除指定位置的节点

移除指定位置的节点也需要判断一下边界条件,可插入节点类似,但要注意移除之后一定要将链表长度-1,代码如下:

// 移除指定位置的元素 this.removeAt = (pos) => {     // 检测边界条件     if(pos >=0 && pos < length) {       let previousNode = null,                current = head,                curIdx = 0;       if(pos === 0) {         // 如果pos为第一个元素         head = current.next       }else {         while(curIdx++ < pos) {           previousNode = current;           current = current.next;         }         previousNode.next = current.next;       }       length --;       return current.el     }else {       return null     } };

2.6 移除指定节点

移除指定节点实现非常简单,我们只需要利用之前实现好的查找节点先找到节点的位置,然后再用实现过的removeAt即可,代码如下:

// 移除指定节点 this.remove = (el) => {   let idx = this.indexOf(el);   this.removeAt(idx); };

2.7 获取节点长度

这里比较简单,直接上代码:

// 返回链表长度 this.size = () => {   return length };

2.8 判断链表是否为空

判断链表是否为空我们只需要判断长度是否为零即可:

// 返回链表长度 this.size = () => {   return length };

2.9 打印节点

打印节点实现方式有很多,大家可以按照自己喜欢的格式打印,这里笔者直接将其打印为数组格式输出,代码如下:

// 将链表转化为数组返回 this.toArray = () => {     let current = head,         results = [];     while(current) {       results.push(current.el);       current = current.next;     }     return results };

这样,我们的单向链表就实现了,那么我们可以这么使用:

let link = new linkedList() // 添加节点 link.append(1) link.append(2) // 查找节点 link.indexOf(2) // ...

3.原生javascript实现一条个双单向链表

有了单向链表的实现基础,实现双向链表也很简单了,我们无非要关注的是双向链表的节点创建,这里笔者实现一个例子供大家参考:

let Node = function(el) {       this.el = el;       this.previous = null;       this.next = null;  } let length = 0 let head = null  // 用来存储头部元素的引用 let tail = null  // 用来存储尾部元素的引用

由代码可知我们在节点中会有上一个节点的引用以及下一个节点的引用,同时这里笔者添加了头部节点和尾部节点方便大家操作。大家可以根据自己的需求实现双向链表的功能,这里笔者提供一份自己实现的代码,可以参考交流一下:

// 双向链表, 每一个元素都有一个存储元素自身的节点和指向上一个元素引用以及下一个元素引用的节点组成 function doubleLinkedList() {   let Node = function(el) {       this.el = el;       this.previous = null;       this.next = null;   }   let length = 0   let head = null  // 用来存储头部元素的引用   let tail = null  // 用来存储尾部元素的引用    // 尾部添加元素   this.append = (el) => {     let node = new Node(el)     if(!head) {       head = node     }else {       tail.next = node;       node.previous = tail;     }     tail = node;     length++   };   // 插入元素   this.insert = (pos, el) => {     if(pos >=0 && pos < length) {       let node = new Node(el);       if(pos === length - 1) {         // 在尾部插入         node.previous = tail.previous;         node.next = tail;         tail.previous = node;         length++;         return true       }       let current = head,           i = 0;       while(i < pos) {         current = current.next;         i++       }       node.next = current;       node.previous = current.previous;       current.previous.next = node;       current.previous = node;       length ++;       return true         }else {       throw new RangeError(`插入范围有误`)     }   };   // 移除指定位置的元素   this.removeAt = (pos) => {     // 检测边界条件     if(pos < 0 || pos >= length) {       throw new RangeError(`删除范围有误`)     }else {       if(length) {         if(pos === length - 1) {           // 如果删除节点位置为尾节点,直接删除,节省查找时间           let previous = tail.previous;           previous.next = null;           length --;           return tail.el         }else {           let current = head,               previous = null,               next = null,               i = 0;           while(i < pos) {             current = current.next             i++           }           previous = current.previous;           next = current.next;           previous.next = next;           length --;           return current.el         }       }else {         return null       }     }   };   // 移除指定节点   this.remove = (el) => {     let idx = this.indexOf(el);     this.removeAt(idx);   };   // 查询指定位置的链表元素   this.get = (index) => {     if(index < 0 || index >= length) {       return undefined     }else {       if(length) {         if(index === length - 1) {           return tail.el         }         let current = head,             i = 0;         while(i < index) {           current = current.next           i++         }         return current.el       }else {         return undefined       }     }   }   // 查询节点所在位置   this.indexOf = (el) => {     let idx = -1,         current = head,         curIdx = -1;     while(current) {       idx++       if(current.el === el) {         curIdx = idx;         break;       }       current = current.next;     }     return curIdx   };   // 判断链表是否为空   this.isEmpty = () => {     return length === 0   };   // 返回链表长度   this.size = () => {     return length   };   // 将链表转化为数组返回   this.toArray = () => {     let current = head,         results = [];     while(current) {       results.push(current.el);       current = current.next;     }     return results   }; }

4.链表和数组的对比及优缺点

实现完链表之后我们会对链表有更深入的认知,接下来我们进一步分析链表的优缺点。笔者将从3个维度来带大家分析链表的性能情况:

  • 插入删除性能

  • 查询性能

  • 内存占用

我们先看看插入和删除的过程:

javascript中链表和数组的详细介绍和使用

javascript中链表和数组的详细介绍和使用

由上图可以发现,链表的插入、删除数据效率非常高,只需要考虑相邻结点的指针变化,因为不需要移动其他节点,时间复杂度是 O(1)。

再来看看查询过程:

javascript中链表和数组的详细介绍和使用

我们对链表进行每一次查询时,都需要从链表的头部开始找起,一步步遍历到目标节点,这个过程效率是非常低的,时间复杂度是  O(n)。这方面我们使用数组的话效率会更高一点。

我们再看看内存占用。链表的内存消耗比较大,因为每个结点除了要存储数据本身,还要储存前后结点的地址。但是好处是可以动态分配内存。

另一方面,对于数组来说,也存在一些缺点,比如数组必须占用整块、连续的内存空间,如果声明的数组数据量过大,可能会导致“内存不足”。其次就是数组一旦需要扩容,会重新申请连续的内存空间,并且需要把上一次的数组数据全部copy到新的内存空间中。

综上所述,当我们的数据存在频繁的插入删除操作时,我们可以采用链表结构来存储我们的数据,如果涉及到频繁查找的操作,我们可以采用数组来处理。实际工作中很多底层框架的封装都是采用组合模式进行设计,一般纯粹采用某种数据结构的比较少,所以具体还是要根据所处环境进行适当的方案设计。

感谢各位的阅读,以上就是“javascript中链表和数组的详细介绍和使用”的内容了,经过本文的学习后,相信大家对javascript中链表和数组的详细介绍和使用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: javascript中链表和数组的详细介绍和使用

本文链接: https://www.lsjlt.com/news/68485.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • javascript中链表和数组的详细介绍和使用
    这篇文章主要讲解了“javascript中链表和数组的详细介绍和使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“javascript中链表和数组的详细介绍...
    99+
    2024-04-02
  • php中链表的详细介绍
    这篇文章主要介绍“php中链表的详细介绍”,在日常操作中,相信很多人在php中链表的详细介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”php中链表的详细介绍”的疑惑有所帮...
    99+
    2024-04-02
  • C++List链表的介绍和使用
    目录1. list的介绍及使用1.1 list的介绍1.2 list的使用1.2.1 list的构造1.2.2 list iterator的使用1.2.3 list capacity...
    99+
    2023-03-07
    C++ List链表 C++ List使用
  • Javascript数组的 forEach 方法详细介绍
    目录前言使用forEach注意事项前言 在JavaScript 中数组的遍历 有很多中方法, 其中有一种 使用 foreach 来遍历数组。 mdn官方文档 语法: arr.forE...
    99+
    2024-04-02
  • Javascript数组的 splice 方法详细介绍
    目录前言牛刀小试删除元素添加元素 并且替换元素example1example2example3example4添加元素example1example2负数索引支持总结前言 splic...
    99+
    2024-04-02
  • vue中keepAlive组件的作用和使用方法详细介绍
    这篇文章主要讲解了“vue中keepAlive组件的作用和使用方法详细介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“vue中keepAlive组件的作用和使用方法详细介绍”吧!前言在面试...
    99+
    2023-06-20
  • JavaScript 中的引用类型Date 和RegExp的详细介绍
    目录引用类型 & 引用值的理解Date 引用类型Date.parse()方法Date.UTC()方法继承的方法RegExpRegExp 实例方法exec()test()继承的...
    99+
    2024-04-02
  • JavaScript函数的详细介绍
    本篇内容主要讲解“JavaScript函数的详细介绍”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“JavaScript函数的详细介绍”吧!一、函数语法一个Jav...
    99+
    2024-04-02
  • Vuex详细介绍和使用方法
    目录一、什么是Vuex二、运行机制三、创建项目1、使用脚手架搭建Vue项目2、安装Vuex3、启动项目4、配置使用Vuex4.1、创建store文件夹4.2、配置全局使用store对...
    99+
    2024-04-02
  • Java中数组下标、遍历和最值的详细介绍
    本篇内容介绍了“Java中数组下标、遍历和最值的详细介绍”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、数组的下标1.什么是数组的下标我们...
    99+
    2023-06-15
  • c语言中数组名a和&a的详细介绍
    这篇文章主要讲解了“c语言中数组名a和&a的详细介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“c语言中数组名a和&a的详细介绍”吧!先说说a和&a的区别(有三点,...
    99+
    2023-06-17
  • java数据结构中单向链表和双向链表的介绍
    这篇文章主要讲解了“java数据结构中单向链表和双向链表的介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“java数据结构中单向链表和双向链表的介绍”吧!目录单向链表单链表图解代码双向链表...
    99+
    2023-06-20
  • Python中lambda表达式的简要介绍和详细使用方法
    Python中lambda函数的简介与用法详解 在Python中,lambda函数是一种特殊的匿名函数,它可以在需要函数对象的任何地方使用。lambda函数通常用来定义一些简单的函数,它们可以只有一个表达式,并且返回结果。本文将...
    99+
    2024-02-02
    简介 用法详解
  • javascript中全局函数的详细介绍
    本篇内容主要讲解“javascript中全局函数的详细介绍”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“javascript中全局函数的详细介绍”吧! ...
    99+
    2024-04-02
  • JavaScript的function函数详细介绍
    通过函数来封装任意多条语句,而且可以在任何地方、任何时间调用执行。 而我们的JavaScript脚本语言比较特殊,相对于C语言,它的参数是不需要数据类型加持的。返回值return,...
    99+
    2024-04-02
  • Redis中GETBIT和SETBIT的详细介绍
    本篇内容介绍了“Redis中GETBIT和SETBIT的详细介绍”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所...
    99+
    2024-04-02
  • c++中string和vector的详细介绍
    目录知识点1【STL的概述】知识点2【迭代器的案例】知识点3【string类】1、案例:string的构造和赋值知识点1【STL的概述】 STL(Standard Template ...
    99+
    2024-04-02
  • JavaScript中的深拷贝和浅拷贝的详细介绍
    这篇文章主要介绍“JavaScript中的深拷贝和浅拷贝的详细介绍”,在日常操作中,相信很多人在JavaScript中的深拷贝和浅拷贝的详细介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,...
    99+
    2024-04-02
  • 详细介绍VB编程中的数组
    在VB(Visual Basic)编程中,数组是一种特殊类型的变量,它用于存储多个相同类型的值。数组可以包含任意数量的元素,这些元素...
    99+
    2023-09-23
    VB
  • Java注解的介绍和使用详细讲解
    文章目录 注解注解基本介绍自定义注解元注解注解解析 注解 注解基本介绍 注解概述: Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 Java 语言中的类、构造器、方法...
    99+
    2023-08-16
    java junit 开发语言
软考高级职称资格查询
推荐阅读
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作