使用 JS 实现单链表

实现单链表中的节点应该具有两个属性: ele  和  nextele  是当前节点的值, next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性  prev 以指示链表中的上一个节点。文本主要实现一个单链表。

要在链表类中实现这些功能

  • push(ele): 向链表尾部添加一个新元素
  • insert(ele, index): 向链表的特定位置插入一个新元素
  • removeAt(index): 根据特定位置,从链表中移除元素
  • remove(ele): 根据元素值,从链表中移除元素
  • modifyValue(ele1, ele2):替换链表中特定元素的值
  • modifyLc(index, val):修改链表中特定位置的元素值
  • getNodeAt(index): 通用方法,返回链表中特定位置的元素
  • getIndexOf(ele): 返回元素在链表中的索引
  • isEmpty(): 判断链表是否为空
  • getHead(): 获取链表第一个元素
  • toString(): 返回表示整个链表的字符串
  • getSize(): 返回链表包含的元素个数

实现

1. 首先定义两个公共方法

/**
 * 创建节点
 * 表示我们想要添加到链表中的项
 */
class CreateNode {
  constructor(ele) {
    this.ele = ele; // 要加入链表元素的值
    // 当一个 Node 实例被创建时,它的 next 指针总是 undefined, 因为我们知道它会是链表的最后一项(push)
    this.next = undefined; // 指向链表中下一个元素的指针
  }
}
/**
 * 比较链表中的元素是否相等
 */
function defaultEquals(a, b) {
  return a === b;
}
  1. CreateNode 类

    • 作用:用于创建链表的节点。
    • 描述:这个类定义了单链表中的节点对象。每个节点都包含两个属性:ele 和 next
      • ele:存储节点的值,即要添加到链表中的项。
      • next:指向链表中下一个节点的指针。在创建节点时,初始化为 undefined,因为此节点可能是链表中的最后一个节点(如果采用尾部插入操作)。
  2. defaultEquals 函数

    • 作用:用于比较链表中的元素是否相等。
    • 描述:这个函数是一个默认的比较函数,用于判断两个元素是否相等。
      • 参数 a 和 b 是待比较的两个元素。
      • 返回 a === b,即如果 a 和 b 相等,则返回 true,否则返回 false

在单链表的操作中,通常会使用这两个方法:

  • CreateNode 类用于创建新的节点对象,以便将其插入到单链表中。每当需要向链表中添加新的元素时,会实例化一个 CreateNode 对象,将要存储的值传入 ele 属性,然后将其添加到链表中。

  • defaultEquals 函数用于在单链表中执行一些操作时,如查找特定元素或删除特定元素时,比较节点中存储的值是否与目标值相等。这个函数在需要比较节点值的场景中是非常有用的,可以根据实际需求重写为自定义的比较函数。

2. 下面就是链表的核心实现

**
 * 创建链表
 * 1. head 头指针
 * 2. 最后一个节点的next始终指向 undefined 或 null
 * 3. 始终只有第一个元素的引用,没有index,需要循环访问列表
 */
class LinkedList {
  constructor(equalsFn = defaultEquals) {
    this.count = 0; // 链表中元素数量,链表的长度
    this.head = undefined; // 第一个元素的引用
    this.equalsFn = equalsFn;
  }
  /**
   * 向链表尾部添加一个新元素
   */
  push(ele) {
    const node = new CreateNode(ele); // CreateNode {ele:123,next:undefined}
    // 定义一个当前指针
    let current;
    if (this.head === undefined || this.head === null) {
      // 链表为空,添加的是第一个元素
      this.head = node;
    } else {
      // 链表不为空,向其追加元素
      // 因为我们始终只有第一个元素的引用,因此需要循环访问列表,直到找到最后一项
      current = this.head;
      while (current.next !== undefined && current.next !== null) {
        current = current.next;
      }
      current.next = node;
    }
    this.count++;
  }

  /**
   * 向链表的特定位置插入一个新元素
   * @param {*} ele
   * @param {*} index
   * @returns true或false
   */

  insert(ele, index) {
    if (index >= 0 && index <= this.count) {
      let newNode = new CreateNode(ele);
      if (index === 0) {
        // 注意需要中间变量current
        const current = this.head;
        newNode.next = current;
        this.head = newNode;
        // 注意直接这么写是错的
        // newNode.next = this.head
        // this.head = newNode;
      } else {
        // 也需要中间变量current
        const previous = this.getNodeAt(index - 1);
        const current = previous.next;
        previous.next = newNode;
        newNode.next = current;
      }
      this.count++;
      return true;
    }
    return false;
  }

  /**
   * 根据特定位置,从链表中移除元素
   * @param {*} index
   * @returns 返回移除的元素,未找到返回undefined
   */
  removeAt(index) {
    // 检查越界值
    if (index >= 0 && index < this.count) {
      let current = this.head;
      // 移除第一项
      if (index === 0) {
        this.head = current.next;
      } else {
        const previous = this.getNodeAt(index - 1);
        current = previous.next;
        // 将previous与current的下一项链接起来,跳过current,从而移除它
        // 当前节点就会被丢弃在计算机内存中,等着被垃圾回收器清除
        previous.next = current.next;
      }
      this.count--;
      return current.ele;
    }
    return undefined;
  }
  
  /**
   * 根据元素值,从链表中移除元素
   * @param {*} ele
   * @returns 返回移除的元素,未找到返回undefined
   */
  remove(ele) {
    const index = this.getIndexOf(ele);
    return this.removeAt(index);
  }
    
   /**
   * 替换链表中特定元素的值
   * @param {*} ele1 要替换的元素的当前值
   * @param {*} ele2 新的元素值
   * @returns {boolean} 若成功替换返回true,否则返回false
   */
   modifyValue(ele1, ele2) {
    let current = this.head;
    let modified = false;

    while (current !== undefined && current !== null) {
      if (this.equalsFn(ele1, current.ele)) {
        current.ele = ele2; // 替换元素的值
        modified = true;
      }
      current = current.next;
    }
   
   /**
   * 修改链表中特定位置的元素值
   * @param {*} index 要修改的元素的位置
   * @param {*} val 新的元素值
   * @returns {boolean} 若成功修改返回true,否则返回false
   */
   modifyLc(index, val) {
    if (index >= 0 && index < this.count) {
      let current = this.getNodeAt(index);
      if (current !== undefined && current !== null) {
        current.ele = val; //修改元素的值
        return true;
      }
    }
    return false;
  }

    return modified;
  }
  /**
   * 返回链表中特定位置的元素(通用)
   * @param {*} index
   * @returns 节点元素
   */
  getNodeAt(index) {
    // 检查越界值
    if (index >= 0 && index <= this.count) {
      // 定义一个当前指针
      let current = this.head;
      // 遍历节点,直到目标位置
      for (let i = 0; i < index; i++) {
        current = current.next;
      }
      return current;
    }
    return undefined;
  }
  
  /**
   * 返回元素在链表中的索引
   * @param {*} ele
   * @returns 返回元素的位置,否则返回-1
   */
  getIndexOf(ele) {
    let current = this.head;
    if (current !== undefined && current !== null) {
      for (let i = 0; i < this.count; i++) {
        // 此处ele可能不是简单数据类型。如果元素是一个复杂对象的话,允许开发者向 LinkedClass 中传入自定义的函数来判断元素是否相等
        if (this.equalsFn(ele, current.ele)) {
          return i;
        } else {
          current = current.next;
        }
      }
      return -1;
    } else {
      return -1;
    }
  }
  
  /**
   * 判断链表是否为空
   * @returns true/false
   */
  isEmpty() {
    return this.getSize() === 0;
  }
  /**
   * 返回链表包含的元素个数,与数组的 length 属性类似
   * @returns number
   */
  getSize() {
    return this.count;
  }
  /**
   * 获取链表第一个元素
   * 因为head 变量是 LinkedList 类的私有变量
   * @returns
   */
  getHead() {
    return this.head;
  }


  /**
   * 返回表示整个链表的字符串
   * 由于列表项使用了 Node 类,就需要重写继承自 JavaScript 对象默认的 toString 方法,让其只输出元素的值。
   * @returns str
   */
  toString() {
    if (this.isEmpty()) {
      return "";
    }
    let objString = `${this.head.ele}`;
    let current = this.head.next;
    for (let i = 1; i < this.getSize() && current != null; i++) {
      objString = `${objString},${current.ele}`;
      current = current.next;
    }
    return objString;
  }
}

// 创建一个链表实例,并添加一些数据
let link = new LinkedList();
link.push(100);
link.push(200);
link.push(300);
link.push(400);
// 打印看一下效果,如下面。
console.log(link); // 可以看出是一个嵌套的树状结构,链表的最后一个节点指向undefined/null

// 继续操作,可以正确获取想要的效果。
link.removeAt(1); // 200
link.insert(200, 1); // true
link.getIndexOf(200); // 1
link.toString(); // "100,200,300,400"


说明

  1. 使用变量引用我们需要控制的节点非常重要,这样就不会丢失节点之间的链接。 如果我们可以只使用一个变量(previous),但那样会很难控制节点之间的链接。因此,最好声明一个额外的变量来帮助我们处理这些引用。如例子中的中间变量 current!

  2. 通过指针的改变,未使用的变量会被自动回收。理解 JavaScript 垃圾回收器如何工作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值