什么是链表
链表是一种线性数据结构,与数组类似,它用于存储一系列元素。不过,与数组在内存中连续存储元素不同,链表中的元素(称为节点)在内存中可以是非连续存放的。每个节点包含两部分:一部分存储数据,另一部分存储指向下一个节点的引用(或指针)。最后一个节点的指针通常指向 null,表示链表结束。
假设我们要创建一个链表来存储一系列整数。链表的第一个节点(头节点)存储数字 1,第二个节点存储数字 2,以此类推。
节点1 -> 节点2 -> 节点3 -> ... -> 节点n
| | | | |
|-----|-----|-----------|---------|
| 1 | → | 2 | → ... → |
| next| | next | |
在这个例子中,每个节点包含两个部分:
- 数据部分(如 1, 2, … n)
- 指向下一个节点的指针(next)
优点
- 动态大小:链表可以根据需要动态地增加或减少节点,无需预先分配固定大小的内存。
- 高效插入与删除:在链表中插入或删除一个元素,只需更改相邻节点之间的指针,时间复杂度可以达到 O(1),而数组中插入或删除元素可能需要移动大量元素。
- 内存利用率高:因为链表只在需要时分配节点,不会造成内存空间的浪费,特别适合存储大量但不确定数量的数据。
缺点
- 访问速度较慢:访问链表中的某个元素需要从头节点开始,逐个遍历直至找到目标节点,时间复杂度为 O(n),而数组可以通过索引直接访问,时间复杂度为 O(1)。
- 额外的存储开销:每个节点除了存储数据外,还需额外的空间来存储指向下一个节点的指针。
- 不支持随机访问:链表不能像数组那样通过索引直接访问元素,降低了某些操作的效率。
- 内存碎片:频繁的插入和删除可能导致内存空间碎片化。
综上所述,链表结构在处理需要频繁插入和删除操作,且不需要快速随机访问数据的场景下更为高效,但在需要快速访问特定位置数据的应用中,其性能不如数组。
编码实战
demo
demo地址: https://tiandisheng.top/utils/music-list
核心代码
// LinkedList.js
class ListNode {
data: any;
next: any;
constructor(data) {
this.data = data;
this.next = null;
}
}
class LinkedList {
head: any;
constructor() {
this.head = null;
}
// 返回链表长度
length() {
let current = this.head;
let count = 0;
while (current) {
count++;
current = current.next;
}
return count;
}
/**
* 新增
*/
append(data) {
const newNode = new ListNode(data);
if (!this.head) {
this.head = newNode;
} else {
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = newNode;
}
}
/**
* 删除
*/
remove(data) {
if (!this.head) return;
if (this.head.data === data) {
this.head = this.head.next;
return;
}
let current = this.head;
while (current.next && current.next.data !== data) {
current = current.next;
}
if (current.next) {
current.next = current.next.next;
}
}
/**
* 在指定位置插入数据
*/
insertAt(data, position) {
if (position < 0 || position > this.length()) {
console.error('Insert position is out of range.');
return;
}
const newNode = new ListNode(data);
if (position === 0) {
newNode.next = this.head;
this.head = newNode;
} else {
let current = this.head;
let prev = null;
for (let i = 0; i < position; i++) {
prev = current;
current = current.next;
}
newNode.next = current;
prev.next = newNode;
}
}
/**
* 置顶
* @param {*} data 要置顶的节点的数据
*/
moveToTop(data) {
if (!this.head) return; // 链表为空时无需操作
// 如果头节点就是要置顶的节点,则无需操作
if (this.head.data === data) return;
// 首先尝试找到待置顶节点及其前驱节点
let current = this.head;
let prev = null;
while (current && current.data !== data) {
prev = current;
current = current.next;
}
if (!current) return; // 数据不存在于链表中
// 如果找到了待置顶的节点
if (prev) {
// 从原位置删除节点
prev.next = current.next;
} else {
// 如果是头节点,则直接更新head
this.head = current.next;
}
// 插入到头部
current.next = this.head;
this.head = current;
}
toArray() {
let current = this.head;
const array: any[] = [];
while (current) {
array.push(current.data);
current = current.next;
}
return array;
}
/**
* @function 将"数组结构"的数据转换为"链表结构"的数据
*/
arrayToLinkList(arrayData: any[]) {
const newLinkList = new LinkedList();
arrayData.forEach((i) => {
newLinkList.append(i);
});
return newLinkList;
}
/**
* @function 将当前链表数据转换为一个新的链表副本
*/
createCopy() {
return this.arrayToLinkList(this.toArray());
}
}
export { LinkedList };