链表

102 阅读4分钟

前端算法小结 | 链表篇

前端算法小结

写在前面

这是本系列小结的第三篇,今天来讲讲数据结构中的链表。

前端开发需要掌握的几种数据结构

还是先罗列一下前端开发所需要的掌握的数据结构:

  •  数组
  •  栈
  •  队列
  •  链表
  •  树(二叉树)

本文将跟大家一起聊聊链表。

链表

链表常常用来与数组作比较。它与数组相似,但是最大的区别在于它是一种非连续的存储结构。数组在内存中一般是占据一段连续的存储空间,而链表中的节点则是分散在存储空间的各个角落里。

对于链表中的节点,我们一般需要关注它以下两部分内容:

该节点本身的数据

该节点的指针指向的下一个节点

在JS中我们常常这样表示链表的数据结构:

JS 中链表的常规操作

创建

链表的创建关键在于链表节点的创建:

添加与删除

在链表中,无论是添加还是删除节点,其实都是在对节点 next 指针进行操作。

添加链表节点分为两种情况:

在链表后面添加节点

这种场景比较简单,不过就是将 next 指向新的节点

在两个节点中间插入节点

在两个已有的节点中间插入新的节点,其实就是把第一个节点的 next 指向新的节点,再把新节点的 next 指向第二个节点

删除节点

删除节点其实就是把被删除节点的前一个节点的next,指向被删除节点原本的next(即被删除节点原本的下一个节点)

访问节点

链表节点的访问就不像节点操作那么轻松了。你必须要明确你要找的是第几个节点,然后再遍历链表去获取到该节点

小结

由上述可知:

  1. 链表中操作节点的复杂度是 O(1),因为它只需要改变被操作节点前后节点的指针指向,不涉及到其他节点的操作
  2. 链表中访问节点的复杂度是 O(n),因为它不像数组一样可以通过下标直接访问节点,而是需要遍历链表才能找到指定的节点

JS 中链表的骚操作

哨兵节点

通常,我们对链表节点的操作都是对节点本身以及其 next 指针的操作。但是在某些特殊场景下,我们还需要对当前节点的前驱节点进行操作。

这中情况下,为了确保链表的第一个节点也能按我们预期的算法去运行,我们就必须要为其创造一个前驱节点。这个思路与上一节 栈和队列篇 中介绍过的哨兵的原理是一样的,因此我也称之为 哨兵节点。(在很多题解中也称之为节点)

快慢指针

快慢指针其实是双指针的其中一种形态。通常情况下,我们借助双指针是为了解决一些比较耗时的操作,比如需要多次遍历的操作,可以借助双指针一次遍历搞定。

而在链表中,比较耗时的操作就是涉及到与链表位置相关的操作了:比如删除指定位置的节点 反转指定位置的链表等等。当遇到这种题时,我们要下意识的往双指针的方向去思考。

快慢指针特指同时从同一方向出发(链表第一个节点出发)的两个指针,其中慢指针走的慢一些(一次向后走一个节点),而快指针走的快一些(一次向后走 n 个节点,n 视情况而定)

反转链表

相信刷过题的各位都看到过这句话哈哈。这充分说明了反转链表在链表题中的地位。

反转链表顾名思义,就是操作一个链表中某段区间内的结点,把这些节点间的 next 指针指向进行反转,将原本指向下一个节点的指针改为指向其前驱结点。

而反转一个链表,我们必须知道三个结点:

  1. 当前结点(currentNode)
  2. 当前结点的前驱结点(preNode)
  3. 当前结点的后继结点(nextNode)

常规的反转操作如下:

而反转链表的操作远不止这些,这后面的真题解析中我会继续深入反转链表。