一、单向链表简介
链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同。链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有的语言称为指针或连接)组成。
数组存在的缺点:
- 数组的创建通常需要申请一段连续的内存空间(一整块内存),并且大小是固定的。所以当原数组不能满足容量需求时,需要扩容(一般情况下是申请一个更大的数组,比如2倍,然后将原数组中的元素复制过去)。
- 在数组的开头或中间位置插入数据的成本很高,需要进行大量元素的位移。
链表的优势:
- 链表中的元素在内存中不必是连续的空间,可以充分利用计算机的内存,实现灵活的内存动态管理。
- 链表不必在创建时就确定大小,并且大小可以无限地延伸下去。
- 链表在插入和删除数据时,时间复杂度可以达到O(1),相对数组效率高很多。
链表的缺点:
- 链表访问任何一个位置的元素时,都需要从头开始访问(无法跳过第一个元素访问任何一个元素)。
- 无法通过下标值直接访问元素,需要从头开始一个个访问,直到找到对应的元素。
- 虽然可以轻松地到达下一个节点,但是回到前一个节点是很难的。
链表中的常见操作:
- append(element):向链表尾部添加一个新的项;
- insert(position,element):向链表的特定位置插入一个新的项;
- get(position):获取对应位置的元素;
- indexOf(element):返回元素在链表中的索引。如果链表中没有该元素就返回-1;
- update(position,element):修改某个位置的元素;
- removeAt(position):从链表的特定位置移除一项;
- remove(element):从链表中移除一项;
- isEmpty():如果链表中不包含任何元素,返回trun,如果链表长度大于0则返回false;
- size():返回链表包含的元素个数,与数组的length属性类似;
- toString():由于链表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值;
二、封装单向链表类
class Node {
constructor(element) {
// 保存元素
this.element = element
// 指向下一个节点
this.next = null
}
}
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) {// 当head指向null时,新结点直接赋值给head
this.head = newNode
} else {
let current = this.head
while (current.next) {//当current.next为null时,current就为最后一项
current = current.next
}
//上面的while循环,一直循环,直到检查到current.next为空,说明当前这项就是链表最后一个了,此时将newNode赋值给最后一项的next
current.next = newNode
}
this.length++
}
// insert(position, element) 向链表的特定位置插入一个新的项。
insert (position, element) {
//1. 判断越界问题
if (position < 0 || position > this.length) return false
//2. 创建新的节点
const newNode = new Node(element)
//3. 插入元素
if (position === 0) {
newNode.next = this.head //newNode成为头部,同时next指向原来的head
this.head = newNode //直接将原来的head覆盖,但由于newNode.next指向原来的head,所以newNode成为head,原来的head顺利成章的成为第二位
} else {
let index = 0
let current = this.head //循环的当前项
let previous = null //循环时,当前项的前一项
while (index++ < position) { //注意:index++返回值为当前index值
/*当index === position时,循环停止
此时element插入链表后的previous的index === position - 1
此时current的index === position + 1
*/
previous = current
current = current.next
}
//循环停止后,index === position,同时为current的index
//进行插入:
previous.next = newNode
newNode.next = current
}
this.length++
return true //告诉使用者插入成功
}
// get(position) 获取对应位置的元素。
get (position) {
//1. 判断越界问题
if (position < 0 || position > this.length - 1) return false
/*
为什么是length - 1 ?
假如:当前length是5, 组成链表的项目下标就为 0 1 2 3 4
如果不减 1 那么 当position也是 5 时候 , 也判断符合要求,但是,我们的链表中却没有 下标为5的项,所以会返回为null
*/
let index = 0
let current = this.head
//方法一:
while (index++ < position) {//如果position 是 3,每次循环index + 1, 第三次循环index的值执行 “++” 后为3
current = current.next
}
//方法二:
/* while (current) {
if (index === position) {
return current
}
index++
current = current.next
}*/
return current
}
// 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) {
//1. 判断越界问题
if (position < 0 || position > this.length - 1) return false
//2.删除元素
let current = this.head
if (position === 0) {
this.head = current.next
} else {
let index = 0
let previous = null
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.将element插入position位置
this.insert(position, element)
return result
}
// remove(element) 从链表中移除一项。
remove (element) {
//1.获取元素位置
const index = this.indexOf(element)
if (index === -1) return
//2.删除该位置的元素
return this.removeAt(index)
}
// isEmpty() 如果链表中不包含任何元素,返回 trun,如果链表长度大于 0 则返回 false。
isEmpty () {
return this.length === 0
}
// size() 返回链表包含的元素个数,与数组的 length 属性类似。
size () {
return this.length
}
}