什么是链表
链表是一种线性数据结构,与数组类似,它用于存储一系列元素。不过,与数组在内存中连续存储元素不同,链表中的元素(称为节点)在内存中可以是非连续存放的。每个节点包含两部分:一部分存储数据,另一部分存储指向下一个节点的引用(或指针)。最后一个节点的指针通常指向 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 {// 如果是头节点,则直接更新headthis.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 };