目录
链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同。
数组的缺点
- 数组的创建通常需要申请一段连续的内存空间(一整块的内存),并且大小是固定的(大多数编程语言数组都是固定的),所以当当前数组不能满足容量需求时,需要扩容.(一般情况下是申请一个更大的数组,比如2倍,然后将原数组中的元素复制过去)
- 而且在数组开头或中间位置插入数据的成本很高,需要进行大量元素的位移,。
- 尽管我们的JavaScript的Array类方法可以帮我们做这些事,但背后的原理依然是这样。
单向链表
链表类似于火车: 有一个火车头,火车头会连接一个节点,节点上有乘客(类似于数据),并且这个节点会连接下-个节点,以此类推.
要存储多个元素,可以选择链表,但不同于数组,链表中的元素在内存中不必是连续的空间
链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有些语言称为指针或者连接)组成
优点
- 内存空间不是必须连续的.可以充分利用计算机的内存,实现灵活的内存动态管理
- 链表不必在创建时就确定大小,并且大小可以无限的延伸下去.
- 链表在插入和删除数据时,时间复杂度可以达到O(1).相对数组效率高很多.
缺点
- 链表访问任何一个位置的元素时,都需要从头开始访问.(无法跳过第一个元素访问任何一个元素).
- 无法通过下标直接访问元素,需要从头一个个访问,直到找到对应的元素.
实现单向链表
export class Node {
constructor(element) {
// 保存元素
this.element = element
// 指向下一个节点
this.next = null
}
}
export class LinkedList {
constructor() {
this.head = null
this.length = 0
}
// append(element): 向列表尾部添加一个新的项
append(element) {
// 1. 根据element创建Node对象
const newNode = new Node(element)
// 2. 追加到最后
if (!this.head) {
this.head = newNode
} else {
let current = this.head
while (current.next) {
current = current.next
}
// next指向下一个节点
current.next = newNode
}
this.length++
}
// insert(position, element): 向列表的特定位置插入一个新的项
insert(position, element) {
if (position < 0 || position > this.length) return false // 链表不能超过默认长度
const newNode = new Node(element)
// 插入元素
if (position == 0) {
newNode.next = this.head
this.head = newNode
} else {
let index = 0
let current = this.head
let previous = null
while (index++ < position) {
previous = current
current = current.next
}
previous.next = newNode
newNode.next = current
}
this.length++
return true
}
// get(position) 获取对应位置的元素
get(position) {
// 判断越界问题
if (position < 0 || position > this.length - 1) return null
// 查找该位置的元素
let index = 0
let current = this.head
while (index++ < position) {
current = current.next
}
return current.element
}
// indexOf(element) 返回元素在列表中的索引,如果列表中没有该元素则返回-1
indexOf(element) {
// 获取第一个元素
let current = this.head
let index = 0
// 开始查找
while (current) {
if (current.element == element) {
return index
}
index++
current = current.next
}
return -1
}
// removeAt(position) 从列表的特定位置移除一项
removeAt(position) {
// 判断越界问题
if (position < 0 || position > this.length - 1) return null
// 删除元素
let current = this.head
let previous = null
let index = 0
if (position === 0) {
this.head = current.next
} else {
while (index++ < position) {
previous = current
current = current.next
}
previous.next = current.next
}
this.length--
return current.element
}
// update(position, element) 修改某个位置的元素
update(position, element) {
// 1. 删除position位置的元素
const result = this.removeAt(position)
// 2. 插入position位置element元素
this.insert(position, element)
return result
}
// remove(element) 从列表中移除一项
remove(element) {
// 获取元素的位置
const index = this.indexOf(element)
if (index === -1) return
// 删除该位置的元素
this.removeAt(index)
}
// isEmpty() 如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false
isEmpty() {
return this.length === 0
}
// size() 返回链表包含的元素个数,与数组的length属性类似
size() {
return this.length
}
}