重读《学习JavaScript数据结构与算法-第三版》- 第6章 链表(一)

简介: 本章为重读《学习JavaScript数据结构与算法》的系列文章,该章节主要讲述数据结构-链表,以及实现链表的过程和原理。

链表


链表,为什么要有这种数据结构呢?当然,事出必有因!


数组-最常用、最方便的数据结构,But,当我们从数组的起点或中间插入或移动项的成本很高,因为我们需要移动数组元素。


链表,是存储有序的元素集合。链表中的元素在内存中并不是连续放置的,每个元素由一个存储自身的元素节点和一个指向下一个元素的引用(指针或链接)组成。


这个是重点,注意,圈起来,面试肯定考!


实现链表


我们要实现链表的结构以及对应的方法,大致为:元素插入、移除、获取链表长度、查询元素、是否为空等等...更详细可看代码实现逻辑。


数据结构越来越复杂喽...注意领会精神...


/**
 * Node 节点类,用于生成自身节点与下一个元素的引用
 */
class Node {
  constructor(element) {
    this.element = element
    this.next = undefined
  }
}
/**
 * defaultEquals() 用于比较两个非对象类的值是否相等
 */
function defaultEquals (a, b) {
  return a === b
}
/**
 * LinkedList 链表
 * 特点:链表存储有序的元素集合,但元素在内存中并不连续存放。每个元素有一个存储元素本身的节点和一个指向下一个元素的引用
 */
class LinkedList {
  constructor (equalsFn = defaultEquals) {
    // 链表长度
    this.count = 0
    // 第一个元素引用
    this.head = undefined
    // 用于比较元素是否相等,默认为defaultEquals,允许传入自定义的函数来比较两个对象是否相等。
    this.equalsFn = equalsFn
  }
}


注意:equalsFn参数,在比较对象类元素时,需要传递自定义方法来比较两个对象的的值是否相等。


push()


向链表的尾部添加元素


/**
 * push() 添加元素到链表
 * @param {*} element 待添加的元素
 */
push (element) {
  let node = new Node(element)
  let current
  // 判断链表是否为空
  if (this.head == null) {
    this.head = node
  } else {
    // 查询最后的元素 - 特点 current.next == null
    current = this.head
    while (current.next != null) {
      current = current.next
    }
    // 将最后元素的下一个元素引用指向node
    current.next = node
  }
  this.count++
} 


getElementAt()


获取指定索引位置的元素


/**
 * getElementAt() 返回索引位置元素
 * @param {Number} index 索引位置
 * @returns {*} node
 */
getElementAt (index) {
  // 判断索引是否有效
  if (index < 0 || index > this.count) {
    return undefined
  }
  let node = this.head
  for (let i = 0; i < index && node != null; i++) {
    node = node.next
  }
  return node
}


insert()


在任意索引位置插入元素


/**
 * insert() 在任意位置插入元素
 * @param {*} element 待插入的元素
 * @param {Number} index 指定的索引位置
 * @returns {Boolean}
 */
insert (element, index) {
  // 判断是否为合法index
  if (index < 0 || index > this.count) {
    return false
  }
  // 实例化节点
  let node = new Node(element)
  // 插入-第一个位置
  if (index === 0) {
    let current = this.head
    node.next = current
    this.head = node
  } else {
    // 查找到指定索引位置的前一个元素
    let previous = this.getElementAt(index - 1)
    let current = previous.next
    // 将上一个元素的next引用指向当前新添加的node,将node的next引用指向current,则完成插入
    previous.next = node
    node.next = current
  }
  // 链表长度+1
  this.count++
  return true
}


removeAt()


移除指定索引位置的元素


实现逻辑:判断是否为链表第一项,若为第一项,则将this.head指向第二个元素即可;如果不是第一项,则获取索引index位置的上一位置索引元素previous,以及下一位置索引元素current.next,将previous.next指向current.next即可


/**
 * removeAt() 移除指定位置的元素
 * @param {Number} index
 * @returns {*} element 移除的元素
 */
removeAt (index) {
  // 判断是否是合法索引
  if (index < 0 || index > this.count) {
    return undefined
  }
  let current = this.head
  // 考虑是否是链表第一项
  if (index === 0) {
    this.head = current.next
  } else {
    // 方法一、 使用for循环迭代获取指定索引位置的元素
    // 用于获取上一位置元素
    // let previous
    // for (let i = 0; i < index; i++) {
    //   previous = current
    //   current = current.next
    // }
    // // 跳过当前元素,将上一个元素的next指向下一个元素
    // previous.next = current.next
    // 方法二、借助getElementAt()方法
    // 查询该索引位置的上一个元素
    let previous = this.getElementAt(index - 1)
    // 跳过中间元素,将上一个元素的next指向下一个元素
    current = previous.next
    previous.next = current.next
  }
  this.count--
  return current.element
}


indexOf()


查询元素的索引位置


实现逻辑:迭代元素,比较每一个元素与目标元素是否相等;特别需要注意的是,对象类的值请传入自定义的方法进行比较是否相等s


/**
 * indexOf() 查询元素的索引位置
 * @param {*} element 待查询的元素
 * @returns {Number} index 索引位置,不存在则返回-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()


移除指定的元素


/**
 * remove() 移除元素
 * @param {*} element 待移除的元素
 * @returns {*} element 移除的元素
 */
remove (element) {
  // 获取索引位置
  let index = this.indexOf(element)
  // 移除指定索引位置的元素
  return this.removeAt(index)
}


size()


获取链表长度


/**
 * size() 返回链表长度
 * @returns {Number} 链表长度
 */
size () {
  return this.count
}


isEmpty()


判断链表是否为空


/**
 * isEmpty() 判断链表是否为空
 * @returns {Boolean}
 */
isEmpty () {
  return this.count === 0
}


getHead()


获取链表首位元素


/**
 * getHead() 获取链表首位元素
 * @returns {*}
 */
getHead () {
  if (this.head != null) {
    return this.head.element
  }
  return undefined
}


clear()


清空链表


/**
 * clear() 清空链表
 */
clear () {
  this.count = 0
  this.head = undefined
}


toString()


链表字符串处理


/**
 * toString() 链表字符串化展示
 * @returns {String}
 */
toString () {
  // 判断是否为空
  if (this.isEmpty()) {
    return ''
  }
  let objStr = `${this.head.element}`
  let current = this.head.next
  for (let i = 1; i < this.count; i++) {
    objStr = `${objStr},${current.element}`
    current = current.next
  }
  return objStr
}


使用链表


let linkedList = new LinkedList()
console.log(linkedList.isEmpty()) // true
// push 尾部压入元素
linkedList.push(15)
linkedList.push(20)
/*
  LinkedList {
    count: 2,
    head: Node {
      element: 15,
      next: Node {
        element: 20,
        next: undefined
      }
    },
    equalsFn: [Function: defaultEquals]
  }
*/
console.log(linkedList)
console.log(linkedList.isEmpty()) // false
// getElementAt() 指定索引位置的元素
let node = linkedList.getElementAt(1)
console.log(node)
// insert() 任意索引位置插入元素
linkedList.insert(22, 1)
linkedList.insert(33, 1)
console.log(linkedList.toString()) // 15,33,22,20
// removeAt() 移除指定索引位置的元素
console.log(linkedList.removeAt(1)) // 33
// remove() 移除元素
console.log(linkedList.remove(15)) // 15
console.log(linkedList.toString()) // 22,20
console.log(linkedList.getHead()) // 22
console.log(linkedList.size()) // 2
console.log(linkedList.toString()) // 22,20
console.log(linkedList.isEmpty()) // false
linkedList.clear()
console.log(linkedList.isEmpty()) // true


再次强调下:如果链表中存储的是对象类型值,请务必传入指定的比较函数equalsFn自行进行比较,You should know why!


相关文章
|
12月前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
213 4
|
8月前
|
算法 数据可视化 开发者
为什么要学习数据结构与算法
今天,我向大家介绍一门非常重要的课程——《数据结构与算法》。这门课不仅是计算机学科的核心,更是每一位开发者从“小白”迈向“高手”的必经之路。
为什么要学习数据结构与算法
|
7月前
|
存储 算法 物联网
解析局域网内控制电脑机制:基于 Go 语言链表算法的隐秘通信技术探究
数字化办公与物联网蓬勃发展的时代背景下,局域网内计算机控制已成为提升工作效率、达成设备协同管理的重要途径。无论是企业远程办公时的设备统一调度,还是智能家居系统中多设备间的联动控制,高效的数据传输与管理机制均构成实现局域网内计算机控制功能的核心要素。本文将深入探究 Go 语言中的链表数据结构,剖析其在局域网内计算机控制过程中,如何达成数据的有序存储与高效传输,并通过完整的 Go 语言代码示例展示其应用流程。
137 0
|
9月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
326 30
|
9月前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
421 25
|
10月前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
472 5
|
11月前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
数据结构与算法系列学习之串的定义和基本操作、串的储存结构、基本操作的实现、朴素模式匹配算法、KMP算法等代码举例及图解说明;【含常见的报错问题及其对应的解决方法】你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
|
12月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
348 5
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
1098 4

热门文章

最新文章