JavaScript 数据结构与算法(六)单向链表

认识链表

链表和数组

链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同。

数组

  • 存储多个元素,数组(或列表)可能是最常用的数据结构。

  • 几乎每一种编程语言都有默认实现数组结构,提供了一个便利的 [] 语法来访问数组元素。

  • 数组缺点:

    数组的创建需要申请一段连续的内存空间(一整块内存),并且大小是固定的,当前数组不能满足容量需求时,需要扩容。 (一般情况下是申请一个更大的数组,比如 2 倍,然后将原数组中的元素复制过去)

    在数组开头或中间位置插入数据的成本很高,需要进行大量元素的位移。

链表

  • 存储多个元素,另外一个选择就是使用链表。

  • 不同于数组,链表中的元素在内存中不必是连续的空间。

  • 链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有些语言称为指针)组成。

  • 链表优点:

    内存空间不必是连续的,可以充分利用计算机的内存,实现灵活的内存动态管理。

    链表不必在创建时就确定大小,并且大小可以无限延伸下去。

    链表在插入和删除数据时,时间复杂度可以达到 O(1),相对数组效率高很多。

  • 链表缺点:

    访问任何一个位置的元素时,需要从头开始访问。(无法跳过第一个元素访问任何一个元素)

    无法通过下标值直接访问元素,需要从头开始一个个访问,直到找到对应的元素。

    虽然可以轻松地到达下一个节点,但是回到前一个节点是很难的。

单向链表

单向链表类似于火车,有一个火车头,火车头会连接一个节点,节点上有乘客,并且这个节点会连接下一个节点,以此类推。

  • 链表的火车结构

  • 链表的数据结构

    head 属性指向链表的第一个节点。
    链表中的最后一个节点指向 null
    当链表中一个节点也没有的时候,head 直接指向 null

  • 给火车加上数据后的结构

链表中的常见操作

  • append(element) 向链表尾部添加一个新的项。
  • insert(position, element) 向链表的特定位置插入一个新的项。
  • get(position) 获取对应位置的元素。
  • indexOf(element) 返回元素在链表中的索引。如果链表中没有该元素就返回-1。
  • update(position, element) 修改某个位置的元素。
  • removeAt(position) 从链表的特定位置移除一项。
  • remove(element) 从链表中移除一项。
  • isEmpty() 如果链表中不包含任何元素,返回 trun,如果链表长度大于 0 则返回 false。
  • size() 返回链表包含的元素个数,与数组的 length 属性类似。
  • toString() 由于链表项使用了 Node 类,就需要重写继承自 JavaScript 对象默认的 toString 方法,让其只输出元素的值。

单向链表的封装

创建单向链表类

先创建单向链表类 LinkedList,添加基本属性,再逐步实现单向链表的常用方法。

class LinkedList {// 初始链表长度为 0length = 0;// 初始 head 为 null,head 指向链表的第一个节点head = null;// 内部类(链表里的节点 Node)Node = class {data;next = null;constructor(data) {this.data = data;}};
}

实现 append() 方法

代码实现
// append() 往链表尾部追加数据
append(data) {// 1、创建新节点const newNode = new this.Node(data);// 2、追加新节点if (this.length === 0) {// 链表长度为 0 时,即只有 head 的时候this.head = newNode;} else {// 链表长度大于 0 时,在最后面添加新节点let currentNode = this.head;// 当 currentNode.next 不为空时,// 循序依次找最后一个节点,即节点的 next 为 null 时while (currentNode.next !== null) {currentNode = currentNode.next;}// 最后一个节点的 next 指向新节点currentNode.next = newNode;}// 3、追加完新节点后,链表长度 + 1this.length++;}
过程图解
  • 首先让 currentNode 指向第一个节点。

  • 通过 while 循环使 currentNode 指向最后一个节点,最后通过 currentNode.next = newNode,让最后一个节点指向新节点 newNode

代码测试
const linkedList = new LinkedList();
// 测试 append 方法
linkedList.append("A");
linkedList.append("B");
linkedList.append("C");
console.log(linkedList);

实现 toString() 方法

代码实现
toString() {let currentNode = this.head;let result = '';// 遍历所有的节点,拼接为字符串,直到节点为 nullwhile (currentNode) {result += currentNode.data + ' ';currentNode = currentNode.next;}return result;
}
代码测试
// 测试 toString 方法
console.log(linkedList.toString()); //--> AA BB CC

实现 insert() 方法

代码实现
// insert() 在指定位置(position)插入节点
insert(position, data) {// position 新插入节点的位置// position = 0 表示新插入后是第一个节点// position = 1 表示新插入后是第二个节点,以此类推// 1、对 position 进行越界判断,不能小于 0 或大于链表长度if (position < 0 || position > this.length) return false;// 2、创建新节点const newNode = new this.Node(data);// 3、插入节点if (position === 0) { // position = 0 的情况// 让新节点的 next 指向 原来的第一个节点,即 headnewNode.next = this.head;// head 赋值为 newNodethis.head = newNode;} else { // 0 < position <= length 的情况// 初始化一些变量let currentNode = this.head; // 当前节点初始化为 headlet previousNode = null; // head 的 上一节点为 nulllet index = 0; // head 的 index 为 0// 在 0 ~ position 之间遍历,不断地更新 currentNode 和 previousNode// 直到找到要插入的位置while (index++ < position) {previousNode = currentNode;currentNode = currentNode.next;}// 在当前节点和当前节点的上一节点之间插入新节点,即它们的改变指向newNode.next = currentNode;previousNode.next = newNode;}// 更新链表长度this.length++;return newNode;
}
代码测试
// 测试 insert 方法
linkedList.insert(0, "123");
linkedList.insert(2, "456");
console.log(linkedList.toString()); //--> 123 AA 456 BB CC

实现 getData() 方法

获取指定位置(position)的 data。

代码实现
getData(position) {// 1、position 越界判断if (position < 0 || position >= this.length) return null;// 2、获取指定 position 节点的 datalet currentNode = this.head;let index = 0;while (index++ < position) {currentNode = currentNode.next;}// 3、返回 datareturn currentNode.data;
}
代码测试
// 测试 getData 方法
console.log(linkedList.getData(0)); //--> 123
console.log(linkedList.getData(1)); //--> AA

实现 indexOf() 方法

indexOf(data) 返回指定 data 的 index,如果没有,返回 -1。

代码实现
indexOf(data) {let currentNode = this.head;let index = 0;while (currentNode) {if (currentNode.data === data) {return index;}currentNode = currentNode.next;index++;}return -1;
}
代码测试
// 测试 indexOf 方法
console.log(linkedList.indexOf("AA")); //--> 1
console.log(linkedList.indexOf("ABC")); //--> -1

实现 update() 方法

update(position, data) 修改指定位置节点的 data。

代码实现
update(position, data) {// 涉及到 position 都要进行越界判断// 1、position 越界判断if (position < 0 || position >= this.length) return false;// 2、痛过循环遍历,找到指定 position 的节点let currentNode = this.head;let index = 0;while (index++ < position) {currentNode = currentNode.next;}// 3、修改节点 datacurrentNode.data = data;return currentNode;
}
代码测试
// 测试 update 方法
linkedList.update(0, "12345");
console.log(linkedList.toString()); //--> 12345 AA 456 BB CC
linkedList.update(1, "54321");
console.log(linkedList.toString()); //--> 12345 54321 456 BB CC

实现 removeAt() 方法

removeAt(position) 删除指定位置的节点。

代码实现
removeAt(position) {// 1、position 越界判断if (position < 0 || position >= this.length) return null;// 2、删除指定 position 节点let currentNode = this.head;if (position === 0) {// position = 0 的情况this.head = this.head.next;} else {// position > 0 的情况// 通过循环遍历,找到指定 position 的节点,赋值到 currentNodelet previousNode = null;let index = 0;while (index++ < position) {previousNode = currentNode;currentNode = currentNode.next;}// 巧妙之处,让上一节点的 next 指向到当前的节点的 next,相当于删除了当前节点。previousNode.next = currentNode.next;}// 3、更新链表长度 -1this.length--;return currentNode;
}
代码测试
// 测试 removeAt 方法
linkedList.removeAt(3);
console.log(linkedList.toString()); //--> 12345 54321 456 CC

实现 remove() 方法

remove(data) 删除指定 data 所在的节点。

代码实现
remove(data) {this.removeAt(this.indexOf(data));
}
代码测试
// 测试 remove 方法
linkedList.remove("CC");
console.log(linkedList.toString()); //--> 12345 54321 456

实现 isEmpty() 方法

isEmpty() 判断链表是否为空。

代码实现
isEmpty() {return this.length === 0;
}
代码测试
// 测试 isEmpty 方法
console.log(linkedList.isEmpty()); //--> false

实现 size() 方法

size() 获取链表的长度。

代码实现
size() {return this.length;
}
代码测试
// 测试 size 方法
console.log(linkedList.size()); //--> 3

完整实现

class LinkedList {// 初始链表长度为 0length = 0;// 初始 head 为 null,head 指向链表的第一个节点head = null;// 内部类(链表里的节点 Node)Node = class {data;next = null;constructor(data) {this.data = data;}};// ------------ 链表的常见操作 ------------ //// append() 往链表尾部追加数据append(data) {// 1、创建新节点const newNode = new this.Node(data);// 2、追加新节点if (this.length === 0) {// 链表长度为 0 时,即只有 head 的时候this.head = newNode;} else {// 链表长度大于 0 时,在最后面添加新节点let currentNode = this.head;// 当 currentNode.next 不为空时,// 循序依次找最后一个节点,即节点的 next 为 null 时while (currentNode.next !== null) {currentNode = currentNode.next;}// 最后一个节点的 next 指向新节点currentNode.next = newNode;}// 3、追加完新节点后,链表长度 + 1this.length++;}// insert() 在指定位置(position)插入节点insert(position, data) {// position 新插入节点的位置// position = 0 表示新插入后是第一个节点// position = 1 表示新插入后是第二个节点,以此类推// 1、对 position 进行越界判断,不能小于 0 或大于链表长度if (position < 0 || position > this.length) return false;// 2、创建新节点const newNode = new this.Node(data);// 3、插入节点if (position === 0) {// position = 0 的情况// 让新节点的 next 指向 原来的第一个节点,即 headnewNode.next = this.head;// head 赋值为 newNodethis.head = newNode;} else {// 0 < position <= length 的情况// 初始化一些变量let currentNode = this.head; // 当前节点初始化为 headlet previousNode = null; // head 的 上一节点为 nulllet index = 0; // head 的 index 为 0// 在 0 ~ position 之间遍历,不断地更新 currentNode 和 previousNode// 直到找到要插入的位置while (index++ < position) {previousNode = currentNode;currentNode = currentNode.next;}// 在当前节点和当前节点的上一节点之间插入新节点,即它们的改变指向newNode.next = currentNode;previousNode.next = newNode;}// 更新链表长度this.length++;return newNode;}// getData() 获取指定位置的 datagetData(position) {// 1、position 越界判断if (position < 0 || position >= this.length) return null;// 2、获取指定 position 节点的 datalet currentNode = this.head;let index = 0;while (index++ < position) {currentNode = currentNode.next;}// 3、返回 datareturn currentNode.data;}// indexOf() 返回指定 data 的 index,如果没有,返回 -1。indexOf(data) {let currentNode = this.head;let index = 0;while (currentNode) {if (currentNode.data === data) {return index;}currentNode = currentNode.next;index++;}return -1;}// update() 修改指定位置节点的 dataupdate(position, data) {// 涉及到 position 都要进行越界判断// 1、position 越界判断if (position < 0 || position >= this.length) return false;// 2、痛过循环遍历,找到指定 position 的节点let currentNode = this.head;let index = 0;while (index++ < position) {currentNode = currentNode.next;}// 3、修改节点 datacurrentNode.data = data;return currentNode;}// removeAt() 删除指定位置的节点removeAt(position) {// 1、position 越界判断if (position < 0 || position >= this.length) return null;// 2、删除指定 position 节点let currentNode = this.head;if (position === 0) {// position = 0 的情况this.head = this.head.next;} else {// position > 0 的情况// 通过循环遍历,找到指定 position 的节点,赋值到 currentNodelet previousNode = null;let index = 0;while (index++ < position) {previousNode = currentNode;currentNode = currentNode.next;}// 巧妙之处,让上一节点的 next 指向到当前的节点的 next,相当于删除了当前节点。previousNode.next = currentNode.next;}// 3、更新链表长度 -1this.length--;return currentNode;}// remove() 删除指定 data 的节点remove(data) {this.removeAt(this.indexOf(data));}// isEmpty() 判断链表是否为空isEmpty() {return this.length === 0;}// size() 获取链表的长度size() {return this.length;}// toString() 链表数据以字符串形式返回toString() {let currentNode = this.head;let result = "";// 遍历所有的节点,拼接为字符串,直到节点为 nullwhile (currentNode) {result += currentNode.data + " ";currentNode = currentNode.next;}return result;}
}

06_JavaScript数据结构与算法(六)单向链表相关推荐

  1. node 获取表单数据 为空_数据结构与算法(python)单向链表篇

    链表 数据表的构建需要预先知道数据的大小来申请连续的存储空间, 而在进行扩充的时候又需要进行数据的搬迁, 使用起来不是很灵活. 链表结构可以充分利用计算机内存空间, 实现灵活的内存动态管理. 简单来说 ...

  2. python数据结构与算法:单向链表

    单链表:python实现及其对应的 增删查检 操作 ##################### P4.1-P4.8 单向链表 ########################### #coding:u ...

  3. java 快速从树节点找到数据_数据结构与算法:单向链表和双向链表

    一.链表简介 1.链表概念 链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的.链表由一系列节点组成,节点可以在运行时动态生成,节点包括两个部分:一个 ...

  4. Java数据结构与算法-SingleLinkedList单向链表插入,删除,查找,修改详解及代码

    SingleLinkedList单向链表插入,删除,查找,修改详解及代码 单向链表学习目标 1. 链表的介绍 2. 单向链表的存储特点以及原理 3. 基本操作:插入,删除等 4. 单向链表应用场景举例 ...

  5. 【数据结构与算法】单向链表的实现

  6. C语言随笔小算法:单向链表

    C语言随笔小算法:单向链表 参考链接: 代码参考:https://blog.csdn.net/go_sann/article/details/80508284 原理参考:https://blog.cs ...

  7. java数据结构与算法之双链表设计与实现

    转载请注明出处(万分感谢!): http://blog.csdn.net/javazejian/article/details/53047590 出自[zejian的博客] 关联文章: java数据结 ...

  8. 数据结构与算法:企业级链表实现(超详细)

    企业级链表介绍 如果我们使用原始的C语言写链表的话,数据类型是被固定死的,如果业务换了 需要另一种数据类型,我们又得重新在写一个链表,当然我们可以使用void* 万能指针,因为void* 可以接受任意 ...

  9. 数据结构与算法:单链表(利用万能指针实现对任意类型数据进行操作)

    前言 C语言的指针真的很强大,万能指针更强大,可以指向任意类型的数据.在上篇博客 数据结构与算法:单链表(超详细实现)中用C语言实现了单链表的相关算法,不过却有局限性 只能针对某一种数据类型还是不够强 ...

  10. 数据结构与算法之单链表

    数据结构与算法之单链表 //链表的实现/*实现单链表的 构建.数据添加.数据删除(返回元素所在位置).数据查找(返回元素所在的位置)的算法设计:*/ //链表的实现/*实现单链表的 构建.数据添加.数 ...

最新文章

  1. RadioButton 自定义控件
  2. Class.isAssignableFrom(Class clz)与instanceof与Class.isInstance(Object obj) 的区别和联系
  3. 用c语言实现存储和读取图片文件,C++实现单张图片读取和保存
  4. 记-crontab定时任务
  5. WWDC2017 笔记 - Cocoa Touch 中的新特性
  6. coldfusion_我从ColdFusion迁移到Java开发
  7. 爬虫小案例:基于Bing关键词批量下载图片(第二版)
  8. Java编程:排序算法——希尔排序
  9. GsonFormat的使用
  10. DB2数据库基本操作
  11. There's code using JDBC based datastore and not disposing them和threadLocal多次访问时,有的时候会访问不到
  12. 设计一个高性能三维渲染服务器,最强大脑-CATIA飞机协同设计制造工作站配置方案2016...
  13. 低效程序员的9个坏习惯
  14. Eclipse代码格式化无效解决方案
  15. vscode下的vue文件格式化
  16. Java虚拟机——Parallel Scavenge收集器
  17. 【强化学习】悬崖寻路:Sarsa和Q-Learning
  18. html首字母样式,CSS实现段落首字母、首字放大特效
  19. Windows Setup could not set the display language
  20. pcl点云特征提取 法线估计 PFH FPFH NARF 惯量偏心矩 RoPs特征 视点特征直方图VFH GASD特征

热门文章

  1. `Algorithm-Solution` `LeetCode` 6305. 二进制矩阵中翻转最多一次使路径不连通
  2. 会计学原理学习笔记——第一章——总论(1.5会计目标)
  3. 论文查重参考文献算不算?
  4. 异步赠书1月书讯:Python/深度学习/贝叶斯/OpenStack/DevOps/Docker
  5. 华为1+X网络系统建设与运维(中级)—— 视频讲解汇总目录
  6. eclips启动code=13的处理经验
  7. android 分享wifi app下载安装,WiFi共享精灵下载
  8. 七律:拜大年//作者:铁哥
  9. 机器学习基础--math(21)--皮亚诺公理
  10. java计算机毕业设计扶贫平台MyBatis+系统+LW文档+源码+调试部署