什么是结点
结点是数据结构中的基本元素,用于存储和表示数据。结点通常包含一个数据项和一个或多个指向其他结点的指针。如图所示:
封装结点
JavaScript是没有直接提供这样的数据结构,但是可以通过class封装出这样的数据结构
class Node{
constructor(data){
this.data = data
this.next = null
}
}
// const node = new Node('4')
// console.log(node);
export default Node
什么是链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 简单来说就是把每个结点连接起来(类比与一节一节的火车,确定好火车头,车厢是可以删除或添加的)
链表的特点
添加,删除数据的效率相比数组要高,查询相比于数组,效率不高只能从链表头或者链表尾遍历查找。
封装链表
包含从链尾添加数据,删除指定位置的数据,删除指定数据,查找指定数据的位置,转换链表为字符串,清空,指定位置插入指定数据,查找链头数据,查询链表长度,判断链表是否为空等方法。
封装如下:(使用模块化语法,注意使用.mjs后缀名)
import Node from "./Node.mjs";
class LinkedList {
//#表示私有属性
//记录链表的长度
#length = 0
//作为链表头,指向null表示链表为空
#head = null
//从链尾添加数据
addBack(data) {
//先创建好结点,找到链尾就插入
const node = new Node(data)
//链表为空时,头即是尾
if (this.#head === null) {
//直接把新结点作为链头
this.#head = node
} else {
//保证链头不丢失,借助临时变量(指向链头)遍历链表
let temp = this.#head
//此时的链表不为空,需要遍历到链尾再插入结点
while (temp.next !== null/*链尾是null*/) {
//临时变量遍历链表
temp = temp.next
}
//遍历结束找到链尾,插入结点
temp.next = node
}
//插入数据,链表长度增加
this.#length++
}
//找到指定位置的结点
getNodeAt(index) {
//先判断该位置是否存在
if (index >= 0 && index < this.#length) {
//利用临时变量从链头遍历链表
let node = this.#head
//遍历到指定位置的结点
for (let i = 0; i < index; i++) {
//变量遍历链表
node = node.next
}
//遍历时,node就是需要寻找的结点
return node
}
return
}
//删除指定位置的数据
removeAt(index) {
//先判断链表中是否有这个位置
if (index >= 0 && index < this.#length) {
//用临时变量存放需要删除的结点,暂时不知道在哪,先存放链头(防止破坏原有的链头)
let current = this.#head
//在链头删除数据
if (index === 0) {
//将原来链表的第二个结点作为链头
this.#head = this.#head.next
} else {
//此时需要删除的结点在链表中
//先找到需要删除结点的上一结点
let previous = this.getNodeAt(index - 1)
//找到需要删除的结点,使用current存放
current = previous.next
//上一结点直接跳过需要删除的结点,直接链上下一结点(删除)
previous.next = current.next
}
//链表删除结点,长度减短
this.#length--
//返回删除结点的数据
return current.data
}
return
}
//删除指定数据
remove(data) {
//先找到该数据的结点所在处
const index = this.indexOf(data)
//删除指定位置的数据
return this.removeAt(index)
}
//找到指定数据的结点索引
indexOf(data) {
//用临时变量从链头遍历到链尾
let current = this.#head
for (let i = 0; i < this.#length; i++) {
//判断指定数据与结点处的数据是否相同
if (this.equal(data, current.data)) {
//返回索引值
return i
}
//变量遍历链表
current = current.next
}
//遍历完说明不存在
return -1
}
//判断数据是否相同
equal(data1, data2) {
//转换成字符串进行判断,此方法存在{a:1,b:2}与{b:2,a:1}会被判断为false的问题
return JSON.stringify(data1) === JSON.stringify(data2)
}
//添加指定数据到指定索引
insert(data, index) {
//判断index是否有效
if (index >= 0 && index <= this.#length/*链表长度为0时允许继续添加数据*/) {
//先创建好结点,找到合适位置就插入
const node = new Node(data)
//在链表头添加数据
if(index === 0){
if(this.#head === null/*如果链表为空*/){
this.#head = node
}else/*链表不为空*/{
//借助临时变量改变链头的结点
let temp = this.#head
//将node插入到链头
node.next = temp
//将node作为新的链头
this.#head = node
}
}else{
//此时在链表中插入结点
//先找到需要插入结点的上一结点
let previous = this.getNodeAt(index - 1)
//用临时变量存放需要插入结点
let temp = previous.next
//在需要插入数据的结点与上一结点之间插入node
node.next = temp
previous.next = node
}
//结点增加,链表增长
this.#length++
return true
}
return false
}
//返回链表的长度
size(){
return this.#length
}
//判断链表是否为空
isEmpty(){
return this.size() === 0
}
//清空链表
clear(){
this.#head = null
this.#length = 0
}
//返回链头
getHead(){
return this.#head
}
//转换为字符串
toString(){
//定义字符串用于拼接
let string = ''
//用临时变量遍历链表
let temp = this.#head
while(temp){
//拼接字符串
string += temp.data + ((temp.next ? ' -> ' : ' -> null'));
temp = temp.next
}
}
}
export default LinkedList