本文已参与「新人创作礼」活动,一起开启掘金创作之路。
在学习任何一门编程语言的时候,我们都必须接触到数据结构,那么今天我们来谈一谈链表这种动态的数据结构,10分钟带你快速了解链表!
前言
其实在javascript这门语言中,官方并没有定义链表这种动态的数据结构,而我们常说的链表是我们通过数组array的方法来实现我们所熟知的链表的这种特殊结构
通常要存储多个元素,数组(Array)可能是最常用的数据结构,数组提供了便利的[ ]语法来访问其中的元素,但是这种数据结构有个明显的缺点: 在大多数语言中数组的大小是固定的,从数组的起点或者中间插入元素或者移除元素花费的成本很高,要对其之后的所有元素进行移动。于是乎我们人为的定义出链表的这种数据结构来帮助我们解决这个问题。
链表
链表存储有序的元素集合,但不同于数组,链表的元素在存储中不是连续放置的。每个元素由一个元素本身的结点和指向下一个元素的引用组成(通常我们也称为链表的前驱和后继 有序链表是线性结构(有且只有一个前驱,有且只有一个后继) )
当然在JS中虽然官方没有定义链表,但是我们学习过程中还是会人为的去定义一些大部分人所接受的语法:
{
val: 1 // 数据域
// 指针域 指向下一节点
next: {
val: 2
next: {
next: ...
}
}
}
function ListNode(val) {
this.val = val
this.next = null // 指向结点
}
数组的增删操作会导致后续所有元素位置变动,所以数组的增删操作时间复杂度就是O(n)
查找某个元素时时间复杂度就是 O(1)
链表的特点
- 有序链表,线性结构(有且只有一个前驱,有且只有一个后继)
- 结点的分布在内存中是离散型的
- 对比数组,链表在添加和删除元素的时候不需要移动多余元素 时间复杂度为O(1)
- 查找某个元素时时间复杂度为 O(n)
我们来体会一下在链表中增删元素的操作:
1. 链表中元素的增加操作
const node1 = new ListNode(1)
const node2 = new ListNode(2)
node1.next = node2
现在需要在node1和node2中间增加一个node3元素,我们应该怎么操作呢?
const node3 = new ListNode(3)
node1.next = node3
node3.next = node2 || node3.next = node.next //因为在插入node3之前,node1.next就是指向node2
这样就成了 node1 --> node3 --> node2(即成功在node12之间插入了node3)
2. 链表中元素的删除操作:
node1 --> node3 --> node2 现在我想删除node3这个元素,应该怎么操作?
node1.next = node3.next || node2
在涉及到链表的删除操作时,重点不是目标节点,而是在于定位目标节点的前驱节点,即在删除node3节点时,我们需要对node1.next进行操作。
数组与链表的区别
数组的存储空间是连续的,也就是我们称之为线性结构,而链表的存储空间是离散型结构,链表里装的是下一个结点的引用地址;即数组的存储地址是连续的,而链表是离散的;
在数组中添加元素相对复杂,虽然我们的代码只需要一行,但是对于引擎而言,要移动添加位置之后的所有的元素,而对于链表而言,本身就是离散型的,所以只需要使得目标节点的前驱节点中间添加一个节点,再将添加的结点的后继指向目标节点。即链表元素的插入删除操作效率明显高于数组元素的插入删除操作的效率;
但是在访问数组和链表的元素时,数组可以直接通过索引查找元素,而链表中,当我们想要访问链表中的任意一个元素,则需要从头(头结点)开始迭代链表,知道找到目标元素即数组元素的访问效率明显高于链表元素的访问效率。
链表的应用
最后让我们来一道力扣上的链表相关的简单算法题来好好理解一下链表:(来源:力扣题库[83. 删除排序链表中的重复元素])
给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回已排序的链表。
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var deleteDuplicates = function (head) {
if (!head) {
return head;
}
let cur = head //定义一个头部变量
while (cur.next) {
// 当链表的当前值与next的值相等时,将cur.next指向cur.next.next(即删除重复项cur.next)
if (cur.val == cur.next.val) {
cur.next = cur.next.next
}else{ //若不相等直接将变量指向下一个要比较的结点
cur = cur.next
}
}
return head; // 最后返回已删除重复项的链表
};
结语
以上就是一些比较简单的链表的相关的概念以及应用,后续还会持续更新,有任何问题都可以在评论区互动交流,感谢您的观看,有任何错误,望大佬们指正,感谢!