一、介绍

JavaScript 原生提供了数组类型,但是却没有链表,虽然平常的业务开发中,数组是可以满足基本需求,但是链表在大数据集操作等特定的场景下明显具有优势,那为何 JavaScript 不提供链表类型呢?怎么实现一个完整可用的链表呢?

数组的特点

  1. 线性结构,顺序存储
  2. 插入慢,查找快
  3. 查找、更新、插入、删除,的时间复杂度分别为,O(1)、O(1)、O(n)、O(n)

链表的特点

  1. 线性结构,随机存储(省内存)
  2. 插入快,查找慢
  3. 查找、更新、插入、删除,的时间复杂度分别为,O(n)、O(1)、O(1)、O(1)

二、单链表

talk is easy, show the code, 下面用 JavaScript 实现一个相对完整的单链表,并提供以下方法供调用:

  1. push(element) // 链表尾部插入节点
  2. pop() // 链表尾部删除节点
  3. shift() // 删除头部节点、
  4. unshift(element) // 插入头部节点
  5. find(index) // 查找指定位置节点
  6. insert(element, index) // 指定位置插入节点
  7. edit(element, index) // 修改指定位置节点
  8. delete(index) // 链表删除指定位置节点
  9. cycle() // 使链表首尾成环
function initList() {
class Node {
constructor(item) {
this.element = item
}
}
class List {
constructor() {
this.head = null
this.size = 0
this.last = null
}
/**
* 链表查找元素
* @param index 查找的位置
*/
find(index) {
let current = this.head
for (let i = 0; i < index; i++) {
current = current.next
}
return current
}
/**
* 链表尾部插入元素
* @param element 插入的元素
*/
push(element) {
let newNode = new Node(element)
if (this.size === 0) {
this.head = newNode
this.head.next = null
this.last = this.head
} else {
this.last.next = newNode
this.last = newNode
newNode.next = null
}
this.size += 1
}
/**
* 链表尾部删除元素
* @param element 删除的位置
*/
pop(element) {
this.last.next = null
}
/**
* 链表头部删除元素
*/
shift() {
if (this.size === 0)
return
this.head = this.head.next
if (this.size === 1)
this.last = null
this.size -= 1
}
/**
* 链表头部插入元素
* @param element 插入的元素
*/
unshift(element) {
let newNode = new Node(element)
newNode.next = this.head
this.head = newNode
if (this.size === 0)
this.last = this.head
this.size += 1
}
/**
* 链表插入元素
* @param element 插入的位置, index 插入的位置
*/
insert(element, index) {
if (index < 0 || index > this.size) {
console.error('超出链表节点范围')
return
}
let newNode = new Node(element)
if (this.size === 0) {
// 空链表
newNode.next = null
this.head = newNode
this.last = newNode
} else if (index === 0) {
// 插入头部
newNode.next = this.head
this.head = newNode
} else if (index == this.size) {
//插入尾部
newNode.next = null
this.last.next = newNode
this.last = newNode
} else {
// 中间插入
let preNode = this.find(index - 1)
newNode.next = preNode.next
preNode.next = newNode
}
this.size += 1
}
/*
*链表编辑元素
* @param element 编辑的元素,index 元素位置
*/
edit(element, index) {
let current = this.find(index)
current.element = element
}
/*
*链表删除元素
* @param index 删除元素位置
*/
delete(index) {
let current = this.find(index)
if (index === 0) {
// 删除头节点
this.head = this.head.next
} else if (index === ((this.size) - 1)) {
// 删除尾节点
let preNode = this.find(index - 1)
preNode.next = null
} else {
// 删除中间节点
let preNode = this.find(index - 1)
let nextNode = preNode.next.next
let removeNode = preNode.next
preNode.next = nextNode
}
this.size -= 1
}
/*
*链表使首尾成环
*/
cycle() {
this.last.next = this.head
}
}
return new List()
} let list = initList()

三、双向链表

双向链表的特点就是添加了指向上一个节点的指针(prev),比较单链表来说,稍微复杂一些,也更强大,这里把上面的单链表修改一下。

function initList() {
class Node {
constructor(item) {
this.element = item
this.next = null
this.prev = null
}
}
class List {
constructor() {
this.head = null
this.size = 0
this.last = null
}
/**
* 链表查找元素
* @param index 查找的位置
*/
find(index) {
let current = this.head
for (let i = 0; i < index; i++) {
current = current.next
}
return current
}
/**
* 链表尾部插入元素
* @param element 插入的元素
*/
push(element) {
let newNode = new Node(element)
if (this.size === 0) {
this.head = newNode
this.head.next = null
this.last = this.head
} else {
this.last.next = newNode
newNode.next = null
newNode.prev = this.last
this.last = newNode
}
this.size += 1
}
/**
* 链表尾部删除元素
* @param element 删除的位置
*/
pop() {
if (this.size === 0)
return
if (this.size === 1) {
this.head = null
this.last = null
} else {
this.last.prev.next = null
this.last = this.last.prev
}
this.size -= 1
}
/**
* 链表头部删除元素
*/
shift() {
if (this.size === 0)
return
if (this.size === 1) {
this.head = null
this.last = null
} else {
this.head = this.head.next
this.head.prev = null
}
this.size -= 1
}
/**
* 链表头部插入元素
* @param element 插入的元素
*/
unshift(element) {
let newNode = new Node(element)
if (this.size === 0) {
this.head = newNode
this.head.next = null
this.last = this.head
} else {
this.head.prev = newNode
newNode.next = this.head
this.head = newNode
}
this.size += 1
}
/**
* 链表插入元素
* @param element 插入的位置, index 插入的位置
*/
insert(element, index) {
if (index < 0 || index > this.size) {
console.error('超出链表节点范围')
return
}
let newNode = new Node(element)
if (this.size === 0) {
// 空链表
this.head = newNode
this.head.next = null
this.last = this.head
} else if (index === 0) {
// 插入头部
this.head.pre = newNode
newNode.next = this.head
this.head = newNode
} else if (index == this.size - 1) {
//插入尾部
newNode.next = null
newNode.prev = this.last
this.last.next = newNode
this.last = newNode
} else {
// 中间插入
let prevNode = this.find(index - 1)
newNode.next = prevNode.next
prevNode.next = newNode
newNode.prev = prevNode
newNode.next.prev = newNode
}
this.size += 1
}
/*
*链表编辑元素
* @param element 编辑的元素,index 元素位置
*/
edit(element, index) {
let current = this.find(index)
current.element = element
}
/*
*链表删除元素
* @param index 删除元素位置
*/
delete(index) {
let current = this.find(index)
if (index === 0) {
// 删除头节点
this.head = this.head.next
this.prev = null
} else if (index === ((this.size) - 1)) {
// 删除尾节点
let preNode = this.find(index - 1)
preNode.next = null
} else {
// 删除中间节点
let preNode = this.find(index - 1)
let nextNode = preNode.next.next
let removeNode = preNode.next
preNode.next = nextNode
nextNode.prev = preNode
}
this.size -= 1
}
/*
*链表使首尾成环
*/
cycle() {
this.last.next = this.head
this.head.prev = this.last
}
}
return new List()
}
let list = new initList()

三、循环链表

循环链表可以是单链表也可以是双向链表,它的特点是最后一个节点的 next 指针指向的是 head 节点

而不是 null,上面代码已经提供了 cycle 方法来实现。

四、判断链表有环

主要有这些方法:

  1. 遍历链表,使每一个节点与之前节点比较,若有重复则为有环链表

  2. 定义一个 Map 对象,遍历链表到每一个节点,若 Map 中没有次节点 ID,则将节点 ID 为 key, 存入 Map ,每个节点判断一次,如果某个节点的 ID存在,证明链表成环

  3. 双指针法,举个例子来说,两个人在操场跑步,速度不同时,总会在某些时刻相遇,就是因为跑到是圆的(环),利用这个原理,定义一个循环和两个指向头节点的指针,一个每次移动一个节点,一个移动两个节点,如果是成环的链表,某个时刻必然会遇到同一个节点。

五、链表在前端开发中的应用

  1. 链表的特性表明其擅长修改,不擅查找,所以对于需要大量修改的操作,可以考虑用链表实现,但是前端往往处理的数据量不会大,所以这种场景的实际意义不是很大,个人感觉在框架的底层优化上,使用较多,业务开发中,数组够用。

  2. 链表因为是随机存储的,所以比较省内存,但是对动态语言 JavaScript 解释器来说,有自动的垃圾回收机制来管理内存,所以链表的这个优势就不明显了。

  3. 链表特殊的结构,感觉适合做轮播图(双向循环链表)、双向导航列表等

【数据结构与算法】--JavaScript 链表的更多相关文章

  1. 《数据结构与算法JavaScript描述》

    <数据结构与算法JavaScript描述> 基本信息 作者: (美)Michael McMillan 译者: 王群锋 杜欢 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:9 ...

  2. 数据结构与算法JavaScript (一) 栈

    序 数据结构与算法JavaScript这本书算是讲解得比较浅显的,优点就是用javascript语言把常用的数据结构给描述了下,书中很多例子来源于常见的一些面试题目,算是与时俱进,业余看了下就顺便记录 ...

  3. 翻阅《数据结构与算法javascript描述》--数组篇

    导读: 这篇文章比较长,介绍了数组常见的操作方法以及一些注意事项,最后还有几道经典的练习题(面试题). 数组的定义: JavaScript 中的数组是一种特殊的对象,用来表示偏移量的索引是该对象的属性 ...

  4. 数据结构与算法javascript描述

    <数据结构与算法javascript描述>--数组篇 导读: 这篇文章比较长,介绍了数组常见的操作方法以及一些注意事项,最后还有几道经典的练习题(面试题). 数组的定义: JavaScri ...

  5. 列表的实现-----数据结构与算法JavaScript描述 第三章

    实现一个列表 script var booklist = new List(); booklist.append('jsbook'); booklist.append('cssbook'); book ...

  6. 《数据结构与算法JavaScript描述》中的一处错误

    最近在看<数据结构与算法JavaScript描述>这本书,看到选择排序这部分时,发现一个比较大的错误. 原书的选择排序算法是这样的: function selectionSort() { ...

  7. 数据结构与算法 Javascript描述

    数据结构与算法系列主要记录<数据结构与算法 Javascript描述>学习心得

  8. Java数据结构和算法(四)--链表

    日常开发中,数组和集合使用的很多,而数组的无序插入和删除效率都是偏低的,这点在学习ArrayList源码的时候就知道了,因为需要把要 插入索引后面的所以元素全部后移一位. 而本文会详细讲解链表,可以解 ...

  9. 读后感:数据结构与算法JavaScript描述

    本书看完,对常见的数据结构与算法从概念上有了更深入的理解. 书中关于数组.栈和队列.链表.字典.散列.集合.二叉树.图.排序.检索.动态规划.贪心算法都有详细的介绍.算是一本不错的学习书籍. 栈和队列 ...

  10. C语言 - 基础数据结构和算法 - 企业链表

    听黑马程序员教程<基础数据结构和算法 (C版本)>,照着老师所讲抄的, 视频地址https://www.bilibili.com/video/BV1vE411f7Jh?p=1 喜欢的朋友可 ...

随机推荐

  1. 【在 Nervos CKB 上做开发】Nervos CKB脚本编程简介[2]:脚本基础

    CKB脚本编程简介[2]:脚本基础 原文作者:Xuejie 原文链接:Introduction to CKB Script Programming 2: Script 本文译者:Shooter,Jas ...

  2. mysql/mariadb 初体验

    距离申请这个博客号已经过了九个月,思前想后还是把知识沉淀放这里吧,不过初心一样,依旧是 '谨以此文,见证成果'.有 兴趣的话也欢迎大家去我的csdn博客转一转.以下是正文: 1.mysql安装 win ...

  3. 《Effective Java第二版》总结

    第1条:考虑用静态工厂方法代替构造器 通常我们会使用 构造方法 来实例化一个对象,例如: // 对象定义 public class Student{ // 姓名 private String name ...

  4. Leetcode之二分法专题-35. 搜索插入位置(Search Insert Position)

    Leetcode之二分法专题-35. 搜索插入位置(Search Insert Position) 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引.如果目标值不存在于数组中,返回它将会 ...

  5. 17_defer(延迟调用)关键字的使用

    1.defer是延迟调用关键字,只能在函数内部使用 2.总是在main函数结束前调用(和init用法相对) 3.如果有多个defer 遵循先进后出的原则 4.和匿名函数同时使用时,如果匿名函数带有参数 ...

  6. 第7章 使用springMVC构建Web应用程序 7.1 springMVC配置

    最近在看spring in action第3版,这里搭建一个简单的spring mvc,也算书没白看,当然老鸟可以不屑了,这里只是给自己做个笔记,配置也尽量删烦就简, Spring MVC的核心是Di ...

  7. os.linesep提取当前平台使用的换行符

    1. unix平台的换行符:\n 2.DOS/Win32平台的换行符:\r\n 3.通过os.linesep函数可以提取当前所处平台的换行符,从而实现不需要关注程序运行在什么平台,也不需要根据不同的平 ...

  8. codeforces 626 G. Raffles(线段树+思维+贪心)

    题目链接:http://codeforces.com/contest/626/problem/G 题解:这题很明显买彩票肯定要买贡献最大的也就是说买p[i]*(num[i]+1)/(num[i]+a[ ...

  9. JSQL查询

    JSQL 其特征与原生soL语句类似,并且完全面向对象,通过类名和属性访问,而不是表名和表的属性. sql:查询的是表和表中的字段 jpql:查询的是实体类和类中的属性 查询全部   >> ...

  10. 接口请求失败处理,重新请求并限制请求次数.自己封装搞定retry函数

    最近开发一款小程序的时候想到一个问题,如果接口突然挂掉怎么办呢,于是乎想到一个解决办法.接口请求重试功能.并限制请求次数 用最新的async函数语法实现.代码简洁明了. 测试代码如下: functio ...