js里面的链表

链表这个词对很多前端开发者来说都是比较陌生的,多出现在c语言个java里面。但是链表并非这些语言独有的。他代表的是一种存储结构,在任何语言都有自己的实现方式,js也不例外。

链表与数组

很多时候链表看起来和数组有些相似,数组其实也是一种存储结构,但是它是一种线性表的顺序存储结构,它的特点是用一组地址连续的存储单元依次存储数据元素。这也是数据可以使用有序的下标获取元素的原因。但是却也正是如此,带来了一些缺陷。每次数次添加或者删除元素,都会对该元素位置后面的所有元素做出位置调整,可能会是一个O(n)的操作。而链表则不会有这种缺陷。链表不要求逻辑上相邻的元素在物理位置上也相邻,它是一种物理存储单元上非连续、非顺序的存储结构,但是也缺失了数组随机读取的优势。

链表有单向链表、双向链表和循环链表

单向链表(指向一个方向)

链表是由一组节点组成的集合。每个节点都使用一个对象的引用指向它的后继。指向另一
个节点的引用叫做链。
一般的链表都会额外添加一个头节点(作为辅助)和尾节点,例如下面这种样子
单向链表
数组元素靠它们的位置进行引用,链表元素则是靠相互之间的关系进行引用。在上图中,我们说 bread 跟在 milk 后面,而不说 bread 是链表中的第二个元素。遍历链表,就是跟着链接,从链表的首元素一直走到尾元(但这不包含链表的头节点,头节点常常用来作为链表的接入点)。上图中另外一个值得注意的地方是,链表的尾元素指向一个 null 节点。

单向链表的特点:

  • 用一组任意的内存空间去存储数据元素(这里的内存空间可以是连续的,也可以是不连续的)
  • 每个节点(node)都由数据本身和一个指向后续节点的指针组成
  • 整个链表的存取必须从头指针开始,头指针指向第一个节点
  • 最后一个节点的指针指向空(NULL)

实现

// 创建节点的构造器(类)
class Node {
  constructor(element) {
    this.element = element;
    this.next = null;
  }
}

// 创建链表
class LinkedList {
  constructor() {
    // 链表一般默认有一个辅助头叫head,尾指向null
    this.head = null;
    // 存储链表长度
    // 这里不同于数组的下标,仅仅代表存储长度,遍历长度可以拿到链表的各个元素
    this.size = 0;
  }
  // 往链表里添加节点(添加到末尾)
  append(element) {
    // 创建一个节点
    let node = new Node(element);
    // 如果head指向null表示这个链表是空的,则将head指向新节点
    if (this.head === null) {
      this.head = node;
    } else {
      // 如果head指向不为空,则需要拿到最后的节点,将next指向新节点
      let curNode = this.getNode(this.size - 1);
      curNode.next = node;
    }
    this.size++;
  }

  // 在指定位置追加
  appendAt(index, element) {
    if (index < 0 || index > this.size) {
      throw new Error("节点添加失败");
    }
    let node = new Node(element);
    if (index === 0) {
      this.head = node;
    } else {
      let preNode = this.getNode(index - 1);
      node.next = preNode.next;
      preNode.next = node;
    }
    this.size++;
  }
  // 删除指定节点
  removeAt(index) {
    if (index < 0 || index > this.size) {
      throw new Error("节点删除失败");
    }
    if (index === 0) {
      this.head = this.head.next;
    } else {
      let preNode = this.getNode(index - 1);
      let curNode = preNode.next;
      preNode.next = curNode.next;
    }

    this.size--;
  }
  // 拿到最后节点的方法。
  getNode(index) {
    // 节点下标不能小于0或者大于链表长度(该下标不同于数组下标,仅仅作为一个标识)
    if ((index < 0) | (index >= this.size)) {
      throw new Error("获取当前节点失败");
    }
    // 获取头部,因为需要遍历节点下标,下标可能为0,所以使用head当作第一个遍历出的值
    let curNode = this.head;
    for (let i = 0; i < index; i++) {
      // 每次遍历将当前节点指向下一个节点
      curNode = curNode.next;
    }
    return curNode;
  }

  indexOf(element) {
    let curNode = this.head;
    for (let i = 0; i < this.size; i++) {
      // 每次遍历将当前节点指向下一个节点
      if (curNode.element === element) {
        return i;
      }
      curNode = curNode.next;
    }
  }
}

测试通过

let l1 = new LinkedList();
l1.append(1);
l1.append(2);
l1.append(3);
l1.append(4);
l1.removeAt(2);
l1.appendAt(2, "fufu");
console.log(l1, l1.indexOf("fufu"));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值