链表结构
1. 背景
在一开始,不知大家用了这么久的数组,你有没有发现数组存在两个明显的缺陷?
-
一个是数组中所有元素的类型必须一致;
-
第二个是数组的元素个数必须事先制定并且一旦指定之后不能更改。
于是乎为了解决数组的缺陷,先辈们发明的一些特殊方法来解决:
- 数组的第一个缺陷靠结构体去解决。结构体允许其中的元素的类型不相同,因此解决了数组的第一个缺陷。所以说结构体是因为数组不能解决某些问题所以才发明的;
结构体是由一批数据组合而成的结构型数据。组成结构型数据的每个数据称为结构型数据的“成员” ,其描述了一块内存区间的大小及解释意义
- 我们希望数组的大小能够实时扩展。譬如我刚开始定了一个元素个数是10,后来程序运行时觉得不够因此动态扩展为20.普通的数组显然不行,我们可以对数组进行封装以达到这种目的;我们还可以使用一个新的数据结构来解决,这个新的数据结构就是链表(几乎可以这样理解:链表就是一个元素个数可以实时变大/变小的数组)。
2. 什么是链表
顾名思义,链表就是用锁链连接起来的表。这里的表指的是一个一个的节点(一个节点可以比喻成火车的一节车厢),节点中有一些内存可以用来存储数据(所以叫表,表就是数据表); 那怎么能更形象的表达一下链表呢?对于这个问题,我们先来看一张图,如下:
存在一个火车头(head)和三个车厢(节点)且相连接,
我们给他抽象一下会得到另外一张图,如下:
由此,我们可以得到一副图形化的链表图:
它由头指针(Head)和若干个节点(节点包括了数据域(data)和指针域(next)通过指针(next)连接起来的一个表。
3. 链表的优势
- 要存储多个元素,除开数组之外也可以使用链表
- 不同于数组,链表中的元素在内存中不必是连续的空间
- 链表的每个元素由一个存储本身的的节点和一个指向下一个元素的引用组成。
3.1 优点(相对于数组)
- 内存空间不必要是连续的。可以充分利用计算机的内存,实现灵活的内存动态管理。
- 链表不必在创建时就确认大小,并且大小可以无限延伸下去
- 链表在插入和删除时,时间复杂度可以达到O(1),相对数组效率很高
3.1 缺点(相对于数组)
- 链表访问任何一个元素时都需要从头开始访问
- 无法通过下标直接访问元素
4. 单链表的基本设计
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FccGiqvF-1660210869909)(E:\Web\test\Notes\数据结构与算法\链表\images\linkedList.png)]
4.1 基本类封装
// 定义节点
class LinkedNode {
constructor(data) {
this.data = data
this.next = null
}
}
// 定义链表
class LinkedList {
constructor() {
// 头节点
this._head = null
// 链表大小
this._size = 0
}
}
接下来,关于链表的操作我们就是一个一个的往LinkedList
类中添加方法。
4.2 链表常用操作
append(ele)
:在表尾插入新节点。insert(position,data)
:向列表中特定位置插入一个新的项。get(position)
:通过下标获取对应位置的节点内容。update(position,data)
:修改指定位置节点内容。indexOf(data)
:获取元素在链表中的位置,返回值为-1表示不存在该元素;removeAt(position)
:移除链表中指定位置的项。remove(data)
:从链表中移除指定项。isEmpty()
:判断链表是否为空,返回值为true表示链表为空;false不为空size()
:获取链表大小toString()
:返回当前链表内容。
4.3 append(ele)
// append(ele):在表尾插入新节点。无返回值。
append(data) {
// 创建新节点
const newNode = new LinkedNode(data)
if (this._size === 0) {
// 判断为添加第一个节点的时候
this._head = newNode
} else {
// 找到最后一个节点,并将新节点插入到其后
let currentNode = this._head
while (currentNode.next) {
currentNode = currentNode.next
}
currentNode.next = newNode
}
this._size += 1
}
4.4 insert(position,data)
// insert(position,data):向列表中特定位置插入一个新的项。
insert(position, data) {
// 1. 越界判断
if (position < 0 || position > this._size) return false
// 2. 创建新节点
const newNode = new LinkedNode(data)
if (position === 0) {
// 3. 判断插入头部的情况
newNode.next = this._head
this._head = newNode
} else {
// 找到需要插入节点的地方并插入
let index = 0
let currentNode = this._head
let previousNode = null
while (index++ < position) {
previousNode = currentNode
currentNode = currentNode.next
}
previousNode.next = newNode
newNode.next = currentNode
}
this._size += 1
return true
}
4.5 get(position)
// get(position):获取对应位置的节点内容
get(position) {
if (position < 0 || position >= this._size) return -1
let index = 0
let currentNode = this._head
while (index++ < position) {
currentNode = currentNode.next
}
return currentNode.data
}
4.6 update(position,data)
// update(position,data):修改指定位置节点内容
update(position, data) {
// 1. 越界判断
if (position < 0 || position >= this._size || this.isEmpty()) return -1
if (position === 0) {
// 2. 更新第一个
this._head.data = data
} else {
// 其他
let index = 0
let currentNode = this._head
while (index++ < position) {
currentNode = currentNode.next
}
currentNode.data = data
}
}
4.7 indexOf(data)
// indexOf(data):获取元素在链表中的位置
indexOf(data) {
// 1. 判断链表是否为空
if (this.isEmpty()) return -1
if (this._size === 1) {
// 当只有一个元素的时候
return this._head.data === data ? 0 : -1
} else {
// 其他
let index = 0
let currentNode = this._head
while (currentNode && currentNode.data !== data) {
currentNode = currentNode.next
index += 1
}
return currentNode ? index : -1
}
}
4.8 removeAt(position)
// removeAt(position):移除链表中指定位置的项
removeAt(position) {
// 1. 越界判断 /非空判断
if (position < 0 || position >= this._size || this._size === 0) return false;
if (position === 0) {
// 2. 链表仅有一个节点
this._head = this._head.next
} else {
// 3. 找到目标节点并删除
let index = 1
let currentNode = this._head.next
let previousNode = this._head
while (index++ < position) {
previousNode = currentNode
currentNode = currentNode.next
}
previousNode.next = currentNode.next
}
this._size -= 1
}
4.9 remove(data)
// remove(data):从链表中移除指定项
remove(data) {
if (this.indexOf(data)) {
this.removeAt(this.indexOf(data))
}
}
4.10 isEmpty()
// isEmpty():判断链表是否为空
isEmpty() {
return !this._size
}
4.11 isEmpty()
// size():获取链表大小
size() {
return this._size
}
4.12 toString()
// 回当前链表的字符串内容
toString() {
let currentNode = this._head
let listStr = ""
while (currentNode) {
listStr += currentNode.data + " "
currentNode = currentNode.next
}
return listStr === "" ? null : listStr
}
4.13 链表演示
var list = new LinkedList()
console.log("new LinkedList:", list.toString() || null);
list.append("aa")
list.append("bb")
list.append("cc")
console.log("append(toString):", list.toString());
list.insert(0, "1")
list.insert(4, "3")
list.insert(2, "2")
console.log("insert(0,4,2):", list.toString());
console.log("get(0,1,2,3,4,5):", list.get(0) + " ", list.get(1) + " ", list.get(2) + " ", list.get(3) + " ", list.get(4) + " ", list.get(5) + " ");
list.update(1, "Aa")
list.update(3, "Bb")
list.update(4, "Cc")
console.log("update(1,3,4):", list.toString());
list.removeAt(0)
list.removeAt(2)
list.removeAt(3)
console.log("removeAt(0,2,3):", list.toString());
console.log("index(Aa,2,Cc,3,4,5):", list.indexOf('Aa') + " ", list.indexOf('2') + " ", list.indexOf('Cc') + " ", list.indexOf('3') + " ", list.indexOf('4') + " ", list.indexOf('5') + " ");
list.remove('Cc')
list.remove('Cc111')
console.log("remove(Cc,Cc111):", list.toString());
console.log("size():", list.size());
console.log("isEmpty():", list.isEmpty());
打印结果:
new LinkedList: null
append(toString): aa bb cc
insert(0,4,2): 1 aa 2 bb cc 3
get(0,1,2,3,4,5): 1 aa 2 bb cc 3
update(1,3,4): 1 Aa 2 Bb Cc 3
removeAt(0,2,3): Aa 2 Cc
index(Aa,2,Cc,3,4,5): 0 1 2 -1 -1 -1
remove(Cc,Cc111): Aa 2
size(): 2
isEmpty(): false
4.14 完整代码
可通过node
执行或者script
引用执行
// 定义节点
class LinkedNode {
constructor(data) {
this.data = data
this.next = null
}
}
// 定义链表
class LinkedList {
constructor() {
// 头节点
this._head = null
// 链表大小
this._size = 0
}
// append(ele):在表尾插入新节点。无返回值。
append(data) {
// 创建新节点
const newNode = new LinkedNode(data)
if (this._size === 0) {
// 判断为添加第一个节点的时候
this._head = newNode
} else {
// 找到最后一个节点,并将新节点插入到其后
let currentNode = this._head
while (currentNode.next) {
currentNode = currentNode.next
}
currentNode.next = newNode
}
this._size += 1
}
// insert(position,data):向列表中特定位置插入一个新的项。
insert(position, data) {
// 1. 越界判断
if (position < 0 || position > this._size) return false
// 2. 创建新节点
const newNode = new LinkedNode(data)
if (position === 0) {
// 3. 判断插入头部的情况
newNode.next = this._head
this._head = newNode
} else {
// 找到需要插入节点的地方并插入
let index = 0
let currentNode = this._head
let previousNode = null
while (index++ < position) {
previousNode = currentNode
currentNode = currentNode.next
}
previousNode.next = newNode
newNode.next = currentNode
}
this._size += 1
return true
}
// get(position):获取对应位置的节点内容
get(position) {
if (position < 0 || position >= this._size) return -1
let index = 0
let currentNode = this._head
while (index++ < position) {
currentNode = currentNode.next
}
return currentNode.data
}
// update(position,data):修改指定位置节点内容
update(position, data) {
// 1. 越界判断
if (position < 0 || position >= this._size || this.isEmpty()) return -1
if (position === 0) {
// 2. 更新第一个
this._head.data = data
} else {
// 其他
let index = 0
let currentNode = this._head
while (index++ < position) {
currentNode = currentNode.next
}
currentNode.data = data
}
}
// indexOf(data):获取元素在链表中的位置
indexOf(data) {
// 1. 判断链表是否为空
if (this.isEmpty()) return -1
if (this._size === 1) {
// 当只有一个元素的时候
return this._head.data === data ? 0 : -1
} else {
// 其他
let index = 0
let currentNode = this._head
while (currentNode && currentNode.data !== data) {
currentNode = currentNode.next
index += 1
}
return currentNode ? index : -1
}
}
// removeAt(position):移除链表中指定位置的项
removeAt(position) {
// 1. 越界判断 /非空判断
if (position < 0 || position >= this._size || this._size === 0) return false;
if (position === 0) {
// 2. 链表仅有一个节点
this._head = this._head.next
} else {
// 3. 找到目标节点并删除
let index = 1
let currentNode = this._head.next
let previousNode = this._head
while (index++ < position) {
previousNode = currentNode
currentNode = currentNode.next
}
previousNode.next = currentNode.next
}
this._size -= 1
}
// remove(data):从链表中移除指定项
remove(data) {
if (this.indexOf(data)) {
this.removeAt(this.indexOf(data))
}
}
// isEmpty():判断链表是否为空
isEmpty() {
return !this._size
}
// size():获取链表大小
size() {
return this._size
}
// toString():返回当前链表内容。
toString() {
let currentNode = this._head
let listStr = ""
while (currentNode) {
listStr += currentNode.data + " "
currentNode = currentNode.next
}
return listStr === "" ? null : listStr
}
}
5. 双链表的基本设计
5.1 基本类封装
// 节点类
class LinkedNode {
constructor(data) {
this.data = data
this.next = null
this.prev = null
}
}
// 链表
class DoubleLinkedList {
constructor() {
this._head = null
this._tail = null
this._size = 0
}
}
接下来,关于链表的操作我们就是一个一个的往LinkedList
类中添加方法。
5.2 链表常用操作
append(ele)
:在表尾插入新节点。insert(position,data)
:向列表中特定位置插入一个新的项。get(position)
:通过下标获取对应位置的节点内容。update(position,data)
:修改指定位置节点内容。indexOf(data)
:获取元素在链表中的位置,返回值为-1表示不存在该元素;removeAt(position)
:移除链表中指定位置的项。remove(data)
:从链表中移除指定项。isEmpty()
:判断链表是否为空,返回值为true表示链表为空;false不为空size()
:获取链表大小forwordString()
:返回正向遍历当前链表内容。backwordString()
:返回反向遍历当前链表内容。
5.3 append(ele)
// append(ele):在表尾插入新节点。无返回值。
append(data) {
// 1. 创建新节点
const newNode = new LinkedNode(data)
// 2. 判断当链表为空的时候
if (this._size === 0) {
this._head = newNode
this._tail = newNode
} else {
this._tail.next = newNode
newNode.prev = this._tail
this._tail = newNode
}
this._size += 1
}
5.4 insert(position,data)
// insert(position,data):向列表中特定位置插入一个新的项。
insert(position, data) {
// 1. 越界判断
if (position < 0 || position > this._size) return false;
// 2. 创建新节点
const newNode = new LinkedNode(data)
if (this._size === 0) {
// 3. 判断列表是否为空
this._head = newNode
this._tail = newNode
} else {
if (position === 0) {
// 4. 判断position为 0 的时候
this._head.prev = newNode
newNode.next = this._head
this._head = newNode
} else if (position === this._size) {
// 5. 判断插入最后的时候
this._tail.next = newNode
newNode.prev = this._tail
this._tail = newNode
} else {
// 6. 找到需要插入节点的地方
let currentNode = this._head
let index = 0
while (index++ < position) {
currentNode = currentNode.next
}
// 7. 插入
newNode.next = currentNode
newNode.prev = currentNode.prev
currentNode.prev.next = newNode
currentNode.prev = newNode
}
}
this._size += 1
return true
}
5.5 get(position)
// get(position):获取对应位置的节点内容
get(position) {
// 1. 越界判断
if (position < 0 || position >= this._size) return false;
if (this._size === 0) {
// 2. 链表为空的时候
return null
} else {
if (position === 0) {
// 3. 获取第一个节点的时候
return this._head.data
} else if (position === this._size - 1) {
// 4. 获取表尾的时候
return this._tail.data
} else {
// 5. 找到指定位置的节点并返回内容
let currentNode = this._head.next
let index = 1
while (index++ < position) {
currentNode = currentNode.next
}
return currentNode.data
}
}
}
5.6 update(position,data)
// update(position,data):修改指定位置节点内容
update(position, data) {
// 1. 越界判断
if (position < 0 || position >= this._size) return false;
if (this._size === 0) {
// 2. 当列表为空的时候
return false
} else {
if (position === 0) {
// 3. 更新第一个节点的时候
this._head.data = data
} else if (position === this._size - 1) {
// 4. 更新最后一个节点
this._tail.data = data
} else {
// 5. 找到节点并更新内容
let currentNode = this._head.next
let index = 1
while (index++ < position) {
currentNode = currentNode.next
}
currentNode.data = data
return true
}
}
}
5.7 indexOf(data)
// indexOf(data):获取元素在链表中的位置
indexOf(data) {
if (this._size === 0) {
// 1. 当链表为空的时候
return -1
} else if (this._size === 1) {
// 2. 当链表只有一个节点的时候
return this._head.data === data ? 0 : -1
} else {
if (this._head.data === data) {
// 3. 当查询的节点为第一个节点时
return 0
} else if (this._tail.data === data) {
// 4. 当查询的节点是最后一个节点时
return this._size - 1
} else {
// 5. 查询剩余节点是否含有传入的节点,有则返回下标,无则返回-1
let currentNode = this._head.next
let index = 1
while (currentNode) {
if (currentNode.data === data) {
return index
}
index += 1
currentNode = currentNode.next
}
return -1;
}
}
}
5.8 removeAt(position)
// removeAt(position):移除链表中指定位置的项
removeAt(position) {
// 1. 越界判断
if (position < 0 || position >= this._size) return false;
if (this._size === 0) {
// 2. 当列表为空的时候
return false
} else if (this._size === 1) {
// 3. 链表仅有一个节点
this._head = null
this._tail = null
} else {
if (position === 0) {
// 4. 删除第一个节点的时候
this._head.next.prev = null
this._head = this._head.next
} else if (position === this._size - 1) {
// 5. 删除最后一个节点
this._tail.prev.next = null
this._tail = this._tail.prev
} else {
// 6. 找到节点并删除内容
let currentNode = this._head.next
let index = 1
while (index++ < position) {
currentNode = currentNode.next
}
currentNode.next.prev = currentNode.prev
currentNode.prev.next = currentNode.next
}
}
// 7. 更新size
this._size -= 1
}
5.9 remove(data)
// remove(data):从链表中移除指定项
remove(data) {
if (this.indexOf(data)) {
this.removeAt(this.indexOf(data))
}
}
5.10 isEmpty()
// isEmpty():判断链表是否为空
isEmpty() {
return !this._size
}
4.11 isEmpty()
// size():获取链表大小
size() {
return this._size
}
5.12 forwordString()
// forwordString():返回正向遍历当前链表内容。
forwordString() {
let currentNode = this._head
let targetString = ""
while (currentNode) {
targetString += currentNode.data + " "
currentNode = currentNode.next
}
return targetString
}
5.13 backwordString()
// backwordString():返回反向遍历当前链表内容。
backwordString() {
let currentNode = this._tail
let targetString = ""
while (currentNode) {
targetString += currentNode.data + " "
currentNode = currentNode.prev
}
return targetString
}
5.14 链表演示
var list = new LinkedList()
console.log("new LinkedList:", list.toString() || null);
list.append("aa")
list.append("bb")
list.append("cc")
console.log("append(toString):", list.toString());
list.insert(0, "1")
list.insert(4, "3")
list.insert(2, "2")
console.log("insert(0,4,2):", list.toString());
console.log("get(0,1,2,3,4,5):", list.get(0) + " ", list.get(1) + " ", list.get(2) + " ", list.get(3) + " ", list.get(4) + " ", list.get(5) + " ");
list.update(1, "Aa")
list.update(3, "Bb")
list.update(4, "Cc")
console.log("update(1,3,4):", list.toString());
list.removeAt(0)
list.removeAt(2)
list.removeAt(3)
console.log("removeAt(0,2,3):", list.toString());
console.log("index(Aa,2,Cc,3,4,5):", list.indexOf('Aa') + " ", list.indexOf('2') + " ", list.indexOf('Cc') + " ", list.indexOf('3') + " ", list.indexOf('4') + " ", list.indexOf('5') + " ");
list.remove('Cc')
list.remove('Cc111')
console.log("remove(Cc,Cc111):", list.toString());
console.log("size():", list.size());
console.log("isEmpty():", list.isEmpty());
打印结果:
new LinkedList: null
append(toString): aa bb cc
insert(0,4,2): 1 aa 2 bb cc 3
get(0,1,2,3,4,5): 1 aa 2 bb cc 3
update(1,3,4): 1 Aa 2 Bb Cc 3
removeAt(0,2,3): Aa 2 Cc
index(Aa,2,Cc,3,4,5): 0 1 2 -1 -1 -1
remove(Cc,Cc111): Aa 2
size(): 2
isEmpty(): false
5.14 完整代码
可通过node
执行或者script
引用执行
// 节点类
class LinkedNode {
constructor(data) {
this.data = data
this.next = null
this.prev = null
}
}
// 链表
class DoubleLinkedList {
constructor() {
this._head = null
this._tail = null
this._size = 0
}
// append(ele):在表尾插入新节点。无返回值。
append(data) {
// 1. 创建新节点
const newNode = new LinkedNode(data)
// 2. 判断当链表为空的时候
if (this._size === 0) {
this._head = newNode
this._tail = newNode
} else {
this._tail.next = newNode
newNode.prev = this._tail
this._tail = newNode
}
this._size += 1
}
// insert(position,data):向列表中特定位置插入一个新的项。
insert(position, data) {
// 1. 越界判断
if (position < 0 || position > this._size) return false;
// 2. 创建新节点
const newNode = new LinkedNode(data)
if (this._size === 0) {
// 3. 判断列表是否为空
this._head = newNode
this._tail = newNode
} else {
if (position === 0) {
// 4. 判断position为 0 的时候
this._head.prev = newNode
newNode.next = this._head
this._head = newNode
} else if (position === this._size) {
// 5. 判断插入最后的时候
this._tail.next = newNode
newNode.prev = this._tail
this._tail = newNode
} else {
// 6. 找到需要插入节点的地方
let currentNode = this._head
let index = 0
while (index++ < position) {
currentNode = currentNode.next
}
// 7. 插入
newNode.next = currentNode
newNode.prev = currentNode.prev
currentNode.prev.next = newNode
currentNode.prev = newNode
}
}
this._size += 1
return true
}
// get(position):获取对应位置的节点内容
get(position) {
// 1. 越界判断
if (position < 0 || position >= this._size) return false;
if (this._size === 0) {
// 2. 链表为空的时候
return null
} else {
if (position === 0) {
// 3. 获取第一个节点的时候
return this._head.data
} else if (position === this._size - 1) {
// 4. 获取表尾的时候
return this._tail.data
} else {
// 5. 找到指定位置的节点并返回内容
let currentNode = this._head.next
let index = 1
while (index++ < position) {
currentNode = currentNode.next
}
return currentNode.data
}
}
}
// update(position,data):修改指定位置节点内容
update(position, data) {
// 1. 越界判断
if (position < 0 || position >= this._size) return false;
if (this._size === 0) {
// 2. 当列表为空的时候
return false
} else {
if (position === 0) {
// 3. 更新第一个节点的时候
this._head.data = data
} else if (position === this._size - 1) {
// 4. 更新最后一个节点
this._tail.data = data
} else {
// 5. 找到节点并更新内容
let currentNode = this._head.next
let index = 1
while (index++ < position) {
currentNode = currentNode.next
}
currentNode.data = data
return true
}
}
}
// removeAt(position):移除链表中指定位置的项
removeAt(position) {
// 1. 越界判断
if (position < 0 || position >= this._size) return false;
if (this._size === 0) {
// 2. 当列表为空的时候
return false
} else if (this._size === 1) {
// 3. 链表仅有一个节点
this._head = null
this._tail = null
} else {
if (position === 0) {
// 4. 删除第一个节点的时候
this._head.next.prev = null
this._head = this._head.next
} else if (position === this._size - 1) {
// 5. 删除最后一个节点
this._tail.prev.next = null
this._tail = this._tail.prev
} else {
// 6. 找到节点并删除内容
let currentNode = this._head.next
let index = 1
while (index++ < position) {
currentNode = currentNode.next
}
currentNode.next.prev = currentNode.prev
currentNode.prev.next = currentNode.next
}
}
// 7. 更新size
this._size -= 1
}
// indexOf(data):获取元素在链表中的位置
indexOf(data) {
if (this._size === 0) {
// 1. 当链表为空的时候
return -1
} else if (this._size === 1) {
// 2. 当链表只有一个节点的时候
return this._head.data === data ? 0 : -1
} else {
if (this._head.data === data) {
// 3. 当查询的节点为第一个节点时
return 0
} else if (this._tail.data === data) {
// 4. 当查询的节点是最后一个节点时
return this._size - 1
} else {
// 5. 查询剩余节点是否含有传入的节点,有则返回下标,无则返回-1
let currentNode = this._head.next
let index = 1
while (currentNode) {
if (currentNode.data === data) {
return index
}
index += 1
currentNode = currentNode.next
}
return -1;
}
}
}
// remove(data):从链表中移除指定项
remove(data) {
if (this.indexOf(data)) {
this.removeAt(this.indexOf(data))
}
}
// isEmpty():判断链表是否为空
isEmpty() {
return !this._size
}
// size():获取链表大小
size() {
return this._size
}
// forwordString():返回正向遍历当前链表内容。
forwordString() {
let currentNode = this._head
let targetString = ""
while (currentNode) {
targetString += currentNode.data + " "
currentNode = currentNode.next
}
return targetString
}
// backwordString():返回反向遍历当前链表内容。
backwordString() {
let currentNode = this._tail
let targetString = ""
while (currentNode) {
targetString += currentNode.data + " "
currentNode = currentNode.prev
}
return targetString
}
}
6. 补充
循环链表: 在链表的基础上,将尾节点的指针指向头结点,就构成了一个循环链表。环形链表从任意一个节点开始,都可以遍历整个链表。 循环单链表与循环双链表的主要不同之处在于单向与双向的区别。
循环单链表:
循环双链表: