「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
今天是关于链表,主要是单项链表这种数据结构表的学习笔记分享。有不足之处或是任何意见建议,欢迎各位大佬不吝斧正~
定义
链表是一种线性表,是一种物理存储单元上非连续、非顺序的存储结构。也就是说,链表不同于数组,在内存中不必是连续的空间。链表中的结点(列表中的元素)的逻辑顺序由每一个结点的指针决定,指针指向的是下一个结点的引用,结点可以在运行时动态生成。示意图如下:
那么为什么我们会需要链表这种数据结构,直接用 js 中已经默认实现的数组它不香吗?下面就来说说数组的一些缺点:
数组的缺点
- 数组的一个主要问题是,在进行中间插入或删除操作的时候,性能比较低。因为数组的创建需要申请一段连续的内存空间,一旦在开头或中间位置插入元素,那么后面的所有元素都需要进行位移以保证连续性,成本比较高。
- 常见的语言中,数组的容量不会自动改变,如果数组装不下新元素了,需要进行扩容操作,比如申请一个更大的数组将原数组复制过去。
- 常见语言的数组还不能存放不同的数据类型。如果自己封装数组一般是在数组中存放 Object 类型,那么其它类型因为都继承自 Object,也就都可以放了。
说完数组的缺点,我们再来说说链表的优点,就可以明白为什么需要自己封装一个链表结构了:
链表的优点
- 不要求连续的内存空间,可以充分利用计算机的内存,实现灵活的内存动态管理。
- 不必在创建时确定大小,结点可以一直增加。
- 在进行插入或删除操作时,时间复杂度可以达到 O(1),效率高于数组。
时间复杂度
这里稍稍解释下时间复杂度的概念,就是用来方便开发者估算出程序的运行时间的衡量标准。O(1) 前面这个是大 O 符号(Big O Notation),O(1) 是最低级别的时间复杂度,比如 a = 1
这种赋值运算,或数组的 push
/pop
操作就属于 O(1) 级别。O(1) 之后还有 O(log n)、O(n) 等,可参考下图所示:
链表的缺点
当然链表也有缺点,就是无法像数组那样通过下标快速查找到某一个位置的结点,而是需要从最开始一个一个的通过指针去查找。
封装
分析
我们可以用一个构造函数 LinkList
来定义链表结构,列表的每个结点则是定义在 LinkList
内部的构造函数 Node
的实例,一个结点中有两个属性:
data
:结点要存储的数据信息;next
:指向下一个结点。
链表本身也有两个属性:
head
:指向链表中的第一个结点,链表所有剩余结点的出发点;length
:记录链表的长度。
原理图如下:
代码
这里先放整体结构及属性的代码,各个方法则在下篇展示并说明。语法上采用的是构造函数形式,算是夯实下 es5 的知识,等实现双向链表再使用 es6 的类。
function LinkList() {
// 结点
function Node(data) {
this.item = data
this.next = null
}
// 属性
this.head = null
this.length = 0
// 方法
// ...
}