和我一起学习前端算法 ——数据结构☞链表

463 阅读5分钟

「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战」。

本来想取名为《前端算法入门》的,但是又担心自己水平不够把大家带沟里了,所以此系列文章就改名为和我一起学习前端算法

我胡汉三又回来啦~今天来讲讲 js 中的链表结构。

链表

链表?什么链表?我读书多,你别想骗我。我就没见过 JS 中还有数据类型叫做链表的,基本数据类型和复杂数据类型我滚瓜烂熟。

别激动别激动,这个链表啊,说的不是数据类型,是一种存储数据的结构,在 JS 的世界里面的确没有直接对应的数据类型。

链表是一种物理存储单元上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

我给翻译成前端语言就是:链表就是一个嵌套的对象,对象的每一层叫做节点,每个节点里面都有下一个节点对象的引用地址。

// JS 世界的链表数据结构长这样
const linkedList = {
    data: 1, // 数据
    next: { // 引用地址
        data: 2,
        next: {
            data: 3,
            next: null, // 最后一个指向空
        }
    }
}

很明显,我们发现,这样的数据结构,如果拿到了一个节点,我们只能知道他的下一个节点是啥,没办法知道上一个节点是啥。

那我就想要整点不一样的✿活呢?非得看看前面节点是个啥。

也行啊,从头开始找呗,找到那个节点的下一个节点是自己不就找到了o((⊙﹏⊙))o.

玩我呢?明明就在隔壁,还得从头开始找,累不累啊

没办法,咱就这条件。如果非得有往前找的需求,那就得请出双向链表了。

双向链表,顾名思义,就是可以双向查找的链表。都说双向奔赴的爱情才是好爱情,双向查找的链表也才是。。。咳咳,不好意思,串台了。。。

那如果双向链表是怎样一个结构呢?聪明的你肯定一下子就想到了,再加一个 pre 属性 引用自己的父对象呗。(o゜▽゜)o☆[BINGO!]

我们可以这样来生成一个双向链表“字面量”:

const head = { pre: null, data: 1, next: null }; // 这就是一个双向链表“字面量”,长度为1
const second = { pre: null, data: 2, next: null }; // 这又来了一个双向链表“字面量”,长度还是为1
// 然后见证奇迹的时刻到了
head.next = second;
second.pre = head;
// 此时 head 变成了一个长度为 2 的双向链表“字面量”,second 变成了 head 的一个节点
// 当然,这个其实什么用都没有,因为 head 没有对应增删改查方法,没有实用价值

你看,只增加了一个属性用来存储父节点指针,就能轻松做到往前查找了。这叫什么?这叫空间换时间啊兄弟,前面的知识点这不就派上用场了嘛o(∩_∩)o 哈哈

使用 JS 模拟一个链表

昨天讲到数组的时候,并没有讲数组的操作方法,因为我觉得数组已经有了 JS 提供的操作 API 了,只要照着用就行了。

然而,对于链表来说,JS 并没有专门的 API 来进行操作,所以这里将简单介绍一下链表的增删改查操作。

由于双向链表和单向链表的操作逻辑大同小异,所以下面只介绍单向链表。

class ListNode {
  constructor(key) {
    this.next = null;
    this.key = key;
  }
}

class List {
  constructor() {
    this.head = null;
    this.length = 0;
  }

  static createNode(key) {
    return new ListNode(key);
  }

  // 往头部插入数据
  insert(node) {
    // 如果head后面有指向的节点
    if (this.head) {
      node.next = this.head;
    } else {
      node.next = null;
    }
    this.head = node;
    this.length++;
  }

  find(key) {
    let node = this.head;
    while (node !== null && node.key !== key) {
      node = node.next;
    }
    return node;
  }

  delete(node) {
    if (this.length === 0) {
      throw 'node is undefined';
    }

    if (node === this.head) {
      this.head = node.next;
      this.length--;
      return;
    }

    let prevNode = this.head;

    while (prevNode.next !== node) {
      prevNode = prevNode.next;
    }

    if (node.next === null) {
      prevNode.next = null;
    }
    if (node.next) {
      prevNode.next = node.next;
    }
    this.length--;
  }
}

好吧,为了节省时间(偷懒),上面的代码是来自集体的智慧,不过我大概看了一下是没有问题的。

结语

链表和数组在其他语言中差别很大,使用场景也有很大的区别。但是对于 JS 来说,由于数组的特殊情况,链表的传统优势并不明显,大部分场景使用数组就足够了。对于学习算法来说,学习 JS 链表可以更方便的看懂其他语言的算法示例。

了解完了链表,接下来再了解一下栈和队列。

本文为系列文章,将和大家一起逐步的学习前端算法相关知识。

系列所有文章都将收录在 专栏 里方便大家查看,大家可以关注我或者收藏本专栏。