js封装数据结构_04_单向链表

本文介绍了如何从零开始实现一个链表,包括添加(append、insert)、删除(remove_identical、removeAt、remove)、修改(update)和查询(get、indexOf)等基本操作。特别地,详细阐述了链表去重的实现,通过转化为数组去重后再重构链表。此外,还提供了isEmpty、size和toString等辅助方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、要想自己封装链表得先知道链表的特点:

链表的每个元素有一个存储元素本身的节点和一个指向下一个节点的引用(有些语言是指针);

链表一般有head、length等属性,一个指向头节点,一个表示链表节点的个数;

链表无法通过下标直接访问元素,需要从head指向的头节点开始一个一个的访问,直到找到对应的元素;

二、这里我实现了:

增:

  • append,向链尾添加节点
  • insert,向特定位置插入新节点

删:

  • remove_identical,链表去重
  • removeAt,指定位置删除节点
  • remove,从列表中移除指定节点

改:

  • update,修改某个位置的节点

查:

  • get,获取对应位置的元素
  • indexOf,返回队应元素的索引,没有则返回-1,有多个以数组形式返回

其他:

  • isEmpty,是否为空
  • size,包含元素个数
  • toString,重写tosTring()方法

三、难的点就说一下链表去重吧:

先把每个节点的数据放到数组里面

然后我们可以把链表去重转换为数组去重,这个简单吧,遍历啊,set啊都是可以实现的

dataArr = Array.from(new Set(dataArr));

然后更新head和length,再重新构造链表即可

四、源码

function LinKedList() {
  // 内部的类:节点类
  function Node(data, next = null) {
    this.data = data;
    this.next = next;
  }

  // 属性
  this.head = null;
  this.length = 0;

  // 方法1:append
  LinKedList.prototype.append = function (data) {
    //1.创建
    let newNode = new Node(data);
    // 2.判断是否是添加的第一个节点
    if (!this.length) {
      this.head = newNode;
    } else {
      // 查找都是从head开始
      let current = this.head;
      //3.末尾的node
      while (current.next) {
        current = current.next;
      }
      // 4.添加:最后节点的next指向最新的节点
      current.next = newNode;
    }
    //5.length++
    this.length++;
  };

  // 方法2:tostring
  LinKedList.prototype.toString = function () {
    let Values = [];
    let current = this.head;
    //将data放入values
    while (current) {
      Values.push(current.data);
      current = current.next;
    }
    //   返回数据,用string也行,这里用数组包裹
    return Values;
  };

  // 方法3:insert
  LinKedList.prototype.insert = function (position, element) {
    // 在插入之前做边界判断
    if (position < 0 && position > this.length) return false;

    // 根据data创建node
    let newNode = new Node(element);

    // 判断是否插入的是第一个
    if (!position) {
      newNode.next = this.head.next;
      this.head = newNode;
    } else {
      //包含了position==this.length这种情况
      let index = 0;
      let current = this.head;
      let previous = null;
      // 拿到当前的和以前的
      while (index++ < position) {
        previous = current;
        current = current.next;
      }
      // 连接节点
      newNode.next = current;
      previous.next = newNode;
    }
    // length++
    this.length++;
    return true;
  };

  // 方法4:get
  LinKedList.prototype.get = function (position) {
    // 是位置都来一个边界判断
    if (position < 0 && position >= this.length) return null;
    let index = 0;
    let current = this.head;
    while (index++ < position) {
      current = current.next;
    }
    return current.data;
  };

  // 方法5:indexOf
  LinKedList.prototype.indexOf = function (element) {
    let index = 0;
    let current = this.head;
    let indexArr = [];
    while (current) {
      let data = current.data;
      if (data == element) {
        indexArr.push(index);
      }
      current = current.next;
      index++;
    }
    if (!indexArr.length) return -1;
    return indexArr;
  };

  // 方法6:update
  LinKedList.prototype.update = function (position, element) {
    if (position < 0 && position >= this.length) return false;
    let index = 0;
    let current = this.head;
    while (index++ < position) {
      current = current.next;
    }
    current.data = element;
    return true;
  };

  // 方法7:removeAt
  LinKedList.prototype.removeAt = function (position) {
    // 边界
    if (position < 0 && position >= this.length) return false;
    let index = 0;
    let current = this.head;
    let previous = null;
    // 0?
    if (!position) {
      this.head = current.next;
      current.next = null;
      return true;
    }

    while (index++ < position) {
      previous = current;
      current = current.next;
    }
    previous.next = current.next;
    current.next = null;
    this.length--;

    return true;
  };

  // 方法8:remove_identical
  LinKedList.prototype.remove_identical = function () {
    let current = this.head;
    let dataArr = [];
    while (current) {
      let data = current.data;
      dataArr.push(data);
      current = current.next;
    }
    current = this.head;
    // 将链表去重转换为数组去重
    dataArr = Array.from(new Set(dataArr));
    // 重新构造链表
    this.head = null;
    this.length = 0;
    for (let i = 0; i < dataArr.length; i++) {
      this.append(dataArr[i]);
    }
    return true;
  };

  // 方法9:remove
  LinKedList.prototype.remove = function (element) {
    let current = this.head;
    let previous = null;
    // 0?
    if (element == current.data) {
      this.head = current.next;
      current.next = null;
      this.index--;
      return true;
    }
    while (current) {
      if (element == current.data) {
        previous.next = current.next;
        current.next = null;
        this.index--;
        return true;
      }
      previous = current;
      current = current.next;
    }

    // 没有该元素,返回false
    return false;
  };

  // 方法10:isEmpty()
  LinKedList.prototype.isEmpty = function () {
    if (!this.length) return true;
    return false;
  };

  // 方法11:size
  LinKedList.prototype.size = function () {
    return this.length;
  };
}

<think>好的,用户想了解如何用JavaScript封装一个单向链表。我需要先回忆单向链表的基本结构和操作。单向链表由节点组成,每个节点包含数据和指向下一个节点的指针。然后,链表类需要实现插入、删除、查找等方法。 首先,我应该定义节点类,每个节点有value和next属性。接下来是链表类,需要有头节点指针,可能还需要记录长度。然后考虑方法,比如append在末尾添加,prepend在头部添加,insert在指定位置插入,remove删除节点,还有查找和反转等方法。 插入和删除需要考虑边界情况,比如头部和尾部的处理。比如,删除头节点时,需要将头节点指向下一个节点。插入到指定位置时,要找到前一个节点,调整指针。 反转链表是一个常见的操作,可以用迭代或递归的方法。这里可能用迭代更直观,维护前一个、当前和下一个节点,逐个反转指针。 然后,考虑测试这些方法。比如创建链表实例,添加节点,插入,删除,反转,然后输出结果是否正确。 需要注意的是,JS中没有指针,所以用对象的引用来模拟指针。另外,要处理索引越界的情况,比如插入或删除时位置是否有效。 可能用户对链表的概念不是很熟悉,所以步骤要详细,代码示例要清晰,并解释每个方法的作用和实现逻辑。还要说明每个方法的时间复杂度,比如插入删除在头部是O(1),其他位置需要遍历是O(n)。 最后,总结单向链表的优缺点,比如插入删除比数组高效,但查找需要遍历。可能用户实际应用场景是什么?比如需要频繁插入删除,不需要随机访问的情况。</think>好的,我将分步骤详细讲解如何用JavaScript实现单向链表,并提供可运行的代码示例: 一、节点类封装 ```javascript class Node { constructor(value) { this.value = value; // 节点存储的值 this.next = null; // 指向下一个节点的指针(默认为null) } } ``` 二、链表类基本框架 ```javascript class LinkedList { constructor() { this.head = null; // 链表头指针 this.length = 0; // 链表长度计数器 } } ``` 三、核心方法实现 1. 尾部追加节点 $$时间复杂度: O(n)$$ ```javascript append(value) { const newNode = new Node(value); if (!this.head) { // 空链表情况处理 this.head = newNode; } else { let current = this.head; while (current.next) { // 遍历到最后一个节点 current = current.next; } current.next = newNode; } this.length++; } ``` 2. 头部插入节点 $$时间复杂度: O(1)$$ ```javascript prepend(value) { const newNode = new Node(value); newNode.next = this.head; // 新节点指向原头节点 this.head = newNode; // 更新头指针 this.length++; } ``` 3. 指定位置插入 $$时间复杂度: O(n)$$ ```javascript insert(index, value) { if (index < 0 || index > this.length) return false; if (index === 0) { this.prepend(value); return true; } const newNode = new Node(value); let prev = this.head; // 找到插入位置的前驱节点 for (let i = 0; i < index - 1; i++) { prev = prev.next; } newNode.next = prev.next; prev.next = newNode; this.length++; return true; } ``` 4. 删除指定位置节点 $$时间复杂度: O(n)$$ ```javascript removeAt(index) { if (index < 0 || index >= this.length) return null; let removedNode; if (index === 0) { removedNode = this.head; this.head = this.head.next; } else { let prev = this.head; for (let i = 0; i < index - 1; i++) { prev = prev.next; } removedNode = prev.next; prev.next = prev.next.next; } this.length--; return removedNode.value; } ``` 5. 链表反转(经典算法) $$时间复杂度: O(n)$$ ```javascript reverse() { let prev = null; let current = this.head; while (current) { const nextTemp = current.next; // 临时保存下一个节点 current.next = prev; // 反转指针方向 prev = current; // 前移prev指针 current = nextTemp; // 前移current指针 } this.head = prev; // 更新头指针 } ``` 四、辅助方法 1. 转换为数组(方便调试) ```javascript toArray() { const result = []; let current = this.head; while (current) { result.push(current.value); current = current.next; } return result; } ``` 2. 查找元素 ```javascript contains(value) { let current = this.head; while (current) { if (current.value === value) return true; current = current.next; } return false; } ``` 五、使用示例 ```javascript const list = new LinkedList(); list.append(10); list.prepend(5); list.insert(1, 7); console.log(list.toArray()); // [5, 7, 10] list.reverse(); console.log(list.toArray()); // [10, 7, 5] list.removeAt(1); console.log(list.toArray()); // [10, 5] ``` 六、应用场景说明 1. 需要频繁在头部插入/删除操作 2. 内存空间不连续的特殊需求 3. 实现撤销操作(通过链表记录操作历史) 4. 实现队列/栈等数据结构的基础 注意事项: 1. 注意处理空链表边界条件 2. 操作前先验证索引有效性 3. 插入/删除后及时更新length属性 4. 使用临时变量保存断开前的指针引用 通过这个实现,您可以掌握链表的核心操作逻辑,后续可根据需求扩展更多方法(如清空链表、获取中间节点等)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值