js使用链表实现音乐播放器(新增,下一首播放,置顶,删除)

什么是链表

链表是一种线性数据结构,与数组类似,它用于存储一系列元素。不过,与数组在内存中连续存储元素不同,链表中的元素(称为节点)在内存中可以是非连续存放的。每个节点包含两部分:一部分存储数据,另一部分存储指向下一个节点的引用(或指针)。最后一个节点的指针通常指向 null,表示链表结束。

假设我们要创建一个链表来存储一系列整数。链表的第一个节点(头节点)存储数字 1,第二个节点存储数字 2,以此类推。

plaintext 复制代码
节点1 -> 节点2 -> 节点3 -> ... -> 节点n
|     |     |           |         |
|-----|-----|-----------|---------|
| 1   | →   |     2     | → ... → |
| next|     | next     |         |

在这个例子中,每个节点包含两个部分:

  • 数据部分(如 1, 2, ... n)
  • 指向下一个节点的指针(next)

优点

  1. 动态大小:链表可以根据需要动态地增加或减少节点,无需预先分配固定大小的内存。
  2. 高效插入与删除:在链表中插入或删除一个元素,只需更改相邻节点之间的指针,时间复杂度可以达到 O(1),而数组中插入或删除元素可能需要移动大量元素。
  3. 内存利用率高:因为链表只在需要时分配节点,不会造成内存空间的浪费,特别适合存储大量但不确定数量的数据。

缺点

  1. 访问速度较慢:访问链表中的某个元素需要从头节点开始,逐个遍历直至找到目标节点,时间复杂度为 O(n),而数组可以通过索引直接访问,时间复杂度为 O(1)。
  2. 额外的存储开销:每个节点除了存储数据外,还需额外的空间来存储指向下一个节点的指针。
  3. 不支持随机访问:链表不能像数组那样通过索引直接访问元素,降低了某些操作的效率。
  4. 内存碎片:频繁的插入和删除可能导致内存空间碎片化。

综上所述,链表结构在处理需要频繁插入和删除操作,且不需要快速随机访问数据的场景下更为高效,但在需要快速访问特定位置数据的应用中,其性能不如数组。

编码实战

demo

demo地址: https://tiandisheng.top/utils/music-list

核心代码

js 复制代码
// 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 };
相关推荐
昵称暂无16 分钟前
.NET 高级开发 | i18n 原理、实现一个 i18n 框架
javascript·c#·.net
三道渊18 分钟前
进程通信与网络协议
开发语言·数据库·php
h_jQuery23 分钟前
vue使用gm-crypto对数据进行sm4加密处理
前端·javascript·vue.js
白露与泡影25 分钟前
Java面试题库及答案解析(2026版)
java·开发语言·面试
疯狂成瘾者42 分钟前
Chroma向量数据库
开发语言·数据库·c#
我是唐青枫1 小时前
C#.NET Monitor 与 Mutex 深入解析:进程内同步、跨进程互斥与使用边界
开发语言·c#·.net
bbq粉刷匠1 小时前
Java--剖析synchronized
java·开发语言
ou.cs1 小时前
c# 信号量和锁的区别
开发语言·c#
Gofarlic_OMS1 小时前
装备制造企业Fluent许可证成本分点典型案例
java·大数据·开发语言·人工智能·自动化·制造
阿赛工作室1 小时前
Vue中onBeforeUnmount不触发的解决方案
前端·javascript·vue.js