资讯专栏INFORMATION COLUMN

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

lmxdawn / 1789人阅读

摘要:前言本章为重读学习数据结构与算法的系列文章,该章节主要讲述数据结构链表,以及实现链表的过程和原理。链表,是存储有序的元素集合。链表中的元素在内存中并不是连续放置的,每个元素由一个存储自身的元素节点和一个指向下一个元素的引用指针或链接组成。

定场诗
伤情最是晚凉天,憔悴厮人不堪言;
邀酒摧肠三杯醉.寻香惊梦五更寒。
钗头凤斜卿有泪,荼蘼花了我无缘;
小楼寂寞新雨月.也难如钩也难圆。
前言

本章为重读《学习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!
后记

以上就是胡哥今天给大家分享的内容,喜欢的小伙伴记得收藏转发、点击右下角按钮在看,推荐给更多小伙伴呦,欢迎多多留言交流...

胡哥有话说,一个有技术,有情怀的胡哥!京东开放平台首席前端攻城狮。与你一起聊聊大前端,分享前端系统架构,框架实现原理,最新最高效的技术实践!

长按扫码关注,更帅更漂亮呦!关注胡哥有话说公众号,可与胡哥继续深入交流呦!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/106961.html

相关文章

  • 重读学习JavaScript数据结构算法-三版》- 3 数组(

    摘要:此处应该有掌声前言读学习数据结构与算法第章数组,本节将为各位小伙伴分享数组的相关知识概念创建方式常见方法以及数组的新功能。数组数组是最简单的内存数据结构,用于存储一系列同一种数据类型的值。 定场诗 大将生来胆气豪,腰横秋水雁翎刀。 风吹鼍鼓山河动,电闪旌旗日月高。 天上麒麟原有种,穴中蝼蚁岂能逃。 太平待诏归来日,朕与先生解战袍。 此处应该有掌声... 前言 读《学习JavaScrip...

    iKcamp 评论0 收藏0
  • 重读学习JavaScript数据结构算法-三版》- 5 队列

    摘要:定场诗马瘦毛长蹄子肥,儿子偷爹不算贼,瞎大爷娶个瞎大奶奶,老两口过了多半辈,谁也没看见谁前言本章为重读学习数据结构与算法第三版的系列文章,主要讲述队列数据结构双端队列数据结构以及队列相关应用。 定场诗 马瘦毛长蹄子肥,儿子偷爹不算贼,瞎大爷娶个瞎大奶奶,老两口过了多半辈,谁也没看见谁! 前言 本章为重读《学习JavaScript数据结构与算法-第三版》的系列文章,主要讲述队列数据结构、...

    charles_paul 评论0 收藏0
  • 重读学习JavaScript数据结构算法-三版》-2 ECMAScriptTypeScr

    摘要:第二种接口的概念和面向对象编程相关接口视为一份合约,在合约里可以定义这份合约的类或接口的行为接口告诉类,它需要实现一个叫做的方法,并且该方法接收一个参数。 定场诗 八月中秋白露,路上行人凄凉; 小桥流水桂花香,日夜千思万想。 心中不得宁静,清早览罢文章, 十年寒苦在书房,方显才高志广。 前言 洛伊安妮·格罗纳女士所著的《学习JavaScript数据结构与算法》第三版于2019年的5月份...

    TZLLOG 评论0 收藏0
  • 重读学习JavaScript数据结构算法-三版》- 3 数组(二)

    摘要:定场诗守法朝朝忧闷,强梁夜夜欢歌损人利己骑马骡,正值公平挨饿修桥补路瞎眼,杀人放火儿多我到西天问我佛,佛说我也没辙前言读学习数据结构与算法第章数组,本小节将继续为各位小伙伴分享数组的相关知识数组的新功能。 定场诗 守法朝朝忧闷,强梁夜夜欢歌; 损人利己骑马骡,正值公平挨饿; 修桥补路瞎眼,杀人放火儿多; 我到西天问我佛,佛说:我也没辙! 前言 读《学习JavaScript数据结构与算法...

    William_Sang 评论0 收藏0
  • JavaScript书籍测评

    摘要:前言今天和大家一起聊聊的推荐书籍,每一本都是精选,做前端开发的朋友们如果没读过,可以尝试一下。如果怕麻烦,也可以关注晓舟报告,发送获取书籍,四个字,就可以得到电子书的提取码。 前言 今天和大家一起聊聊JavaScript的推荐书籍,每一本都是精选,做前端开发的朋友们如果没读过,可以尝试一下。下面给大家简单介绍了书的内容,还有读书的方法,希望可以帮大家提升读书效率。 一、《JavaScr...

    X1nFLY 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<