Javascript常见数据结构三——链表
四、链表
1.创建链表结构
当我们使用的数组数据量比较大的时候 ,我们往数组前面插入一个数据,在这之后的所有元素的索引都要向后挪,这个时候,效率是很低的。
链表 很好的解决了这个问题 链表中的所有数据只与他的下一个数据有关。执行插入操作的时候,无论数据量有多少,我们只需要断开链子,将新插入的元素与插入位置的两端的元素连接起来就可以了
适用于:当数据比较庞大的时候,并且需要多次执行插入操作 此时选择链表 性能会高于数组
功能需求分析:
添加节点 append
查找结点 find
指定数据后面添加节点 insert
移除指定数据对应的节点 remove
打印所有数据 print
返回链表长度 size
let LinkedList = (function () {
// Node 用来存储数据和下一个节点
class Node {
constructor(data) {
this.data = data
this.next = null
}
}
// Symbol 解决外界访问
let HEAD = Symbol()
return class {
constructor() {
this[HEAD] = null
}
// 添加数据
append(data) {
let node = new Node(data)
let head = this[HEAD]
// 如果 链表头部为空
if (!head) {
this[HEAD] = node
return
}
// 如果链表头部不为空 需要找到链表尾部 将数据插入进去
// 新数据添加到链表尾部
while (head.next) {
head = head.next
}
head.next = node
}
// 查找结点
find(index) {
let head = this[HEAD]
let i = 0
while (head) {
if (i === index) {
return head
}
head = head.next
i++
}
return null
}
// 指定数据插入
insertByData(data, newData) {
// 找到链表头部
let head = this[HEAD]
// 新数据
let node = new Node(newData)
// 找到 data 对应的数据
let thisNode
while (head && head.data !== data) {
head = head.next
}
// 如果找不到 data 对应的数据
if (!head) {
// 找不到对应的数据
console.warn('当前链表不存在对应的数据')
return
}
// 得到 next 之后 需要断开head和 next的链条 然后插入新数据
let next = head.next
head.next = node
node.next = next
}
// 指定数据移除
removeByData(data){
let head = this[HEAD]
while(head && head.next){
// 寻找指定的data数据
if(head.next.data === data){
return head.next = head.next.next
}
// 移步到下一个数据
head = head.next
}
}
// 指定序号插入
insertById(index,newData){
// 待插入的节点
let node = new Node(newData)
// 找到当前序号对应的节点
let nodeAfter = this.find(index)
// 找到序号对应的前一个节点
let nodeBefore
if(index === 0){
// index 是 0
this[HEAD] = node
}else{
// index 不是0
nodeBefore = this.find(index-1)
if(!nodeBefore){
console.warn("指定索引超出链表长度")
return
}
nodeBefore.next = node
}
node.next = nodeAfter
}
// 指定序号移除
remove(index){
let thisNode = this.find(index)
if(index===0){
this[HEAD] = thisNode.next
}else{
let NodeBefore = this.find(index-1)
// 判断NodeBefore 是否存在 不存在则是超出了当前链表长度
if(!NodeBefore){
console.warn('指定索引超出链表长度')
return
}
NodeBefore && (NodeBefore.next = thisNode.next)
}
}
// 打印数据
print() {
let head = this[HEAD]
let arr = []
while (head) {
arr.push(head.data)
head = head.next
}
// 返回值有多种形式 在这里 我就返回一个数组
return arr
}
// 链表长度
size(){
let size = 0
let head = this[HEAD]
while(head){
head = head.next
size++
}
return size
}
}
})()
2.双向链表
双向链表的开发效率会优于链表,也是比较常用的一种数据结构。
双向链表在链表的基础上进行了优化,每个节点可以拿到当前节点的前后两个节点,插入操作不需要在进行循环遍历,极大的提高了性能,我们平时使用的也比较多
let LinkedList = (function () {
class Node {
constructor(data, linked) {
this.data = data
this.linked = linked
this.next = null
this.prev = null
}
// 当前数据之前插入数据
insertBefore(data) {
let prev = this.prev
// console.log('前一个节点为:', prev)
// console.log('this', this)
let node = new Node(data, this.linked)
if (prev) {
// 前一个存在
// console.log('前一个节点存在')
prev.next = node
node.prev = prev
} else {
// 前一个不存在 说明当前节点为头节点
// console.log('前一个节点不存在')
this.linked[HEAD] = node
}
this.prev = node
node.next = this
this.linked.length++
return node
}
// 当前数据之后插入数据
insertAfter(data) {
let next = this.next
let node = new Node(data, this.linked)
if (next) {
// 后一个节点存在
node.next = next
next.prev = node
} else {
// 后一个节点不存在 说明当前节点为为节点
this.linked[FOOT] = node
}
this.next = node
node.prev = this
// 插入成功后 length ++
this.linked.length++
return node
}
// 删除当前数据
remove(){
let prev = this.prev
let next = this.next
// 如果prev 不存在 那么this链表头部
if(!prev){
this.linked[HEAD] = next
}
// 如果next不存在 那么this为链表尾部
if(!next){
this.linked[FOOT] = prev
}
prev && (prev.next = next)
next && (next.prev = prev)
this.linked.length--
}
}
// 定义 头部 尾部 标记
// 暴露给外界 但是不可以进行操作
const HEAD = Symbol('head')
const FOOT = Symbol('foot')
return class {
constructor() {
this[HEAD] = null
this[FOOT] = null
this.length = 0
}
// 链表添加数据
append(data) {
let node = new Node(data, this)
if (this[FOOT]) {
this[FOOT].next = node
node.prev = this[FOOT]
this[FOOT] = node
} else {
this[HEAD] = node
}
this[FOOT] = node
this.length ++
return node
}
// 查询链表数据
print(){
let head = this[HEAD]
let arr = []
while(head){
arr.push(head.data)
head = head.next
}
return arr
}
}
})()