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 };
相关推荐
legend_jz15 分钟前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
小镇程序员27 分钟前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
tangliang_cn36 分钟前
java入门 自定义springboot starter
java·开发语言·spring boot
程序猿阿伟37 分钟前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒39 分钟前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪1 小时前
AJAX的基本使用
前端·javascript·ajax
新知图书1 小时前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
威威猫的栗子1 小时前
Python Turtle召唤童年:喜羊羊与灰太狼之懒羊羊绘画
开发语言·python
力透键背1 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript
bluefox19791 小时前
使用 Oracle.DataAccess.Client 驱动 和 OleDB 调用Oracle 函数的区别
开发语言·c#