持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
为什么要使用链表?
存储多个元素,数组可能是最常用的一种数据结构,但是数组有两个缺点:
- 在数组的开头或者中间插入/删除数据,需要大量的元素后移,内存开支较大。
- 数组在声明时就确定了一块大小固定且空间连续的内存空间,这块空间如果用不完,就会造成浪费;如果不够用,就会进行扩容处理,翻倍扩大数组的容量。
为了解决这两个数组的痛点,引出了“链表”这种数据结构。
优点:
- 链表不必在创建时就确定大小。
- 内存空间不必是连续的,可以充分的利用计算机的内存,实现灵活的内存动态管理。
- 链表在插入和删除数据时,时间复杂度很低,最低可达到O(1)
缺点:
- 无法通过下标访问元素,也就是访问任意地方的元素,都需要按照链表顺序,从头开始顺延下去。
链表类的封装
function linkedList(){
// head记录头指针
this.head=null
// 链表不同于数组,长度需要自己手动记录
this.length=0
// 封装一个内部类,记录节点
function Node(data){
// data保存节点的信息
this.data=data
// next保存指针,也就是如何到下一个节点
this.next=null
}
}
toString()
重写一下tostring方法,方便查看链表里的内容。
如果不重写toString直接打印,则是这种情况
// toString()
LinkedList.prototype.toString = function () {
let current = this.head
let listString = ''
// 对当前节点是否存在进行判断
while (current) {
current.next
? listString += current.data + '-'
: listString += current.data
current = current.next
}
return listString
}
append()
在链表的最后增加一个节点
// 链表追加方法
LinkedList.prototype.append = function (data) {
// 创建新的节点
const newNode = new Node()
// 判断原链表是否为空
// 为空,直接更改头指针的指向
if (this.length == 0) this.head = newNode
// 不为空,则找到最后一个节点,让其指针指向新节点
else {
const current = this.head
// 如果节点的指向不为undefined,则current变成下个节点
while (current.next) current = current.next
current.next = newNode
}
// 链表长度+1
this.length++
}
insert()
向链表的指定位置插入元素。需要传入位置参数和data参数。
// 链表插入
LinkedList.prototype.insert = function (position, data) {
// 对要插入的位置进行一个越界判断
if (position < 0 || position > this.length) return false
const newNode = new Node(data)
// 如果插入的位置在第0项,则要修改this.head的指向
if (position == 0) {
current.next = this.head
this.head = current
}
else {
// 如果不是,则通过循环找到要插入的位置,
// 将前一个节点的指针指向新节点
// 将新节点的指针指向原来位置的节点
let previous = null// 记录前一个节点
let current = this.head
let index = 0// 作为找到position的循环判断媒介
while (index++ < position) {
previous = current
current = current.next
}
previous.next = newNode
newNode.next = current
}
// 注意长度+1
this.length++
return true
}
get()
传入一个位置参数,获取该位置处的元素内容。
// 根据位置获取节点内容
LinkedList.prototype.get = function (position) {
// 越界判断
if (position < 0 || position >= this.length) return null
let current = this.head
let index = 0
// 通过循环找到position处的节点
while (index++ < position) current = current.next
return current.data
}
indexOf()
传进去一个代表内容的参数,返回该内容在链表中第一次出现的位置。
// 根据内容查找第一次出现的位置
LinkedList.prototype.indexOf = function (data) {
let current = this.head
let index = 0// 记录位置信息
while (current) {
if (current.data == data) return index
current = current.next
index++
}
return -1//找不到就返回“-1”
}
update()
传入两个参数,一个是要更新的元素的位置,一个是更新的内容。
循环到要更新的元素位置后,更改元素的内容。
// 更新元素
LinkedList.prototype.update = function (position, content) {
if (position < 0 || position >= this.length) return false
let index = 0
let current = this.head
// 当找到节点后,修改当前节点的data
while (index++ < position) current = current.next
current.data = content
return true
}
removeAt
传入一个位置参数,删除该位置的元素。
// 从链表某一位置移除一项元素
LinkedList.prototype.removeAt = function (position) {
if (position < 0 || position >= this.length) return false
let current = this.head // 保存当前项元素
let previous = null // 保存当前项元素的上一项元素
// 当移除的是第一项元素时,修改头指针的指向
if (position == 0) this.head = current.next
else {
let index = 0
while (index++ < position) {
previous = current
current = current.next
}
// 当找到要删除的元素后,将它的上一项元素的指针指向它的下一项元素
previous.next = current.next
}
// 因为删除了一项元素,注意长度-1
this.length--
return true
}
remove
LinkedList.prototype.remove = function (data) {
// 调用indexOf方法,根据data获得元素的position
const index = this.indexOf(data)
// 调用removeAt方法,根据position移除元素
const flag = index == -1 ? false : this.removeAt(index)
// 短路运算,且找假。如果移除了元素,就将长度减一
flag && this.length--
return flag
}
但是这里面有个误区,就是因为在remove里面调用了removeAt,所以就用不对长度进行减一的操作了。
LinkedList.prototype.remove = function (data) {
const index = this.indexOf(data)
return this.removeAt(index)
}
因为越界判断等操作,removeAt里面都已经做好了。