JS 数据结构 —— 单向链表(上篇)

362 阅读3分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。

今天是关于链表,主要是单项链表这种数据结构表的学习笔记分享。有不足之处或是任何意见建议,欢迎各位大佬不吝斧正~

定义

链表是一种线性表,是一种物理存储单元上非连续、非顺序的存储结构。也就是说,链表不同于数组,在内存中不必是连续的空间。链表中的结点(列表中的元素)的逻辑顺序由每一个结点的指针决定,指针指向的是下一个结点的引用,结点可以在运行时动态生成。示意图如下:
yuque_diagram.jpg
那么为什么我们会需要链表这种数据结构,直接用 js 中已经默认实现的数组它不香吗?下面就来说说数组的一些缺点:

数组的缺点

  1. 数组的一个主要问题是,在进行中间插入或删除操作的时候,性能比较低。因为数组的创建需要申请一段连续的内存空间,一旦在开头或中间位置插入元素,那么后面的所有元素都需要进行位移以保证连续性,成本比较高。
  2. 常见的语言中,数组的容量不会自动改变,如果数组装不下新元素了,需要进行扩容操作,比如申请一个更大的数组将原数组复制过去。
  3. 常见语言的数组还不能存放不同的数据类型。如果自己封装数组一般是在数组中存放 Object 类型,那么其它类型因为都继承自 Object,也就都可以放了。
    说完数组的缺点,我们再来说说链表的优点,就可以明白为什么需要自己封装一个链表结构了:

链表的优点

  1. 不要求连续的内存空间,可以充分利用计算机的内存,实现灵活的内存动态管理。
  2. 不必在创建时确定大小,结点可以一直增加。
  3. 在进行插入或删除操作时,时间复杂度可以达到 O(1),效率高于数组。

时间复杂度

这里稍稍解释下时间复杂度的概念,就是用来方便开发者估算出程序的运行时间的衡量标准。O(1) 前面这个是大 O 符号(Big O Notation),O(1) 是最低级别的时间复杂度,比如 a = 1 这种赋值运算,或数组的 push/pop 操作就属于 O(1) 级别。O(1) 之后还有 O(log n)、O(n) 等,可参考下图所示: image.png

链表的缺点

当然链表也有缺点,就是无法像数组那样通过下标快速查找到某一个位置的结点,而是需要从最开始一个一个的通过指针去查找。

封装

分析

我们可以用一个构造函数 LinkList 来定义链表结构,列表的每个结点则是定义在 LinkList 内部的构造函数 Node 的实例,一个结点中有两个属性:

  1. data:结点要存储的数据信息;
  2. next:指向下一个结点。

链表本身也有两个属性:

  1. head:指向链表中的第一个结点,链表所有剩余结点的出发点;
  2. length:记录链表的长度。

原理图如下: yuque_diagram (1).jpg

代码

这里先放整体结构及属性的代码,各个方法则在下篇展示并说明。语法上采用的是构造函数形式,算是夯实下 es5 的知识,等实现双向链表再使用 es6 的类。

function LinkList() {
  // 结点
  function Node(data) {
    this.item = data
    this.next = null
  }

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

  // 方法
  // ...
}

感谢.gif 点赞.png