链表与数组
- 要存储多个元素,数组或者链表是最常用的数据结构。
- 数组这种数据结构非常方便,我们通过js语法能快速的增删改查。然而在大多数语言中,数组的大小是固定的,从数组的起点或者中间插入或者删除的成本很高,并且需要移动元素
- 链表存储有的元素集合,但和数组不同,链表中的元素在内存中不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(指针)组成。相较于数据,链表的好处在于,添加或者删除元素的时候不需要移动其他元素。在数组中,我们可以通过下标直接访问任何位置的元素,而想要访问链表中间的元素,则需要从表头开始迭代链表直到找到所需要的元素
| 操作 | 数组(时间复杂度) | 链表(时间复杂度) |
|---|---|---|
| 头插(vector没有此操作) | O(1) | O(1) |
| push_back | O(1) | O(1) |
| insert | O(n) | O(1) |
| erase | O(n) | O(1) |
| 随机访问 | O(1) | O(n) |
链表
链表的基本介绍
- 链表的种类
- 分为:单向链表、双向链表、循环链表、双向循环链表。下面我们先来介绍最简单的单向链表
- 链表的定义:文章最开始介绍了什么是链表,那再用更通俗的语言来解释一下,链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成
- 数据域:存放数据
- 指针域:存放指向下一个结点的指针
//代码定义链表结构-函数
function ListNode(val, next) {
this.val = val
this.next = (!next ? null : next)
}
//代码定义链表结构-类
class ListNode{
constuctor(val,next){
this.val=val;
this.next=next
}
}
- 链表的存储方式:链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理
链表的操作
- 查找节点
//node为给定查找的节点值
function find ( node ) {
const currentNode = this.head;
while ( currentNode.val !== node ){
currentNode = currentNode.next;
}
return currentNode;
}
- 添加节点
- 如果从尾部添加,只需要将当前尾部的next,指向要添加的元素
- 如果在中间添加,把添加的节点的next指向想要添加位置后的一个节点,然后把想要添加的位置前一个节点的next指向添加节点(注意:两次操作不能颠倒,如果先将前一个节点指向想要添加的节点,那我们就丢失原来的指向)
- 删除节点
- 将当前想要删除节点的前一个节点的指针指向当前删除节点的后一个节点
链表的应用
- 操作系统的动态内存分配
- 当内存空间有4GB,我们申请使用1GB的内存,原有的内存空间生成两个内存碎片,使用链表来管理剩余的内存碎片空间,这样就不会丢掉任何一片内存区域
- LRU 缓存淘汰算法
- 缓存
- cpu有两种取数据的方式,最原始的在硬盘中获取数据,但硬盘对于cpu来讲,读取速度非常慢。第二种方式我们可以吧cpu经常使用的数据放在我们的内存当中,内存对于cpu来讲,读取速度非常快。如果使用第二种方式,那么内存对于硬盘来讲,就是缓存空间。缓存也就是高速设备之于低速设备的一种称呼
- cpu有两种取数据的方式,最原始的在硬盘中获取数据,但硬盘对于cpu来讲,读取速度非常慢。第二种方式我们可以吧cpu经常使用的数据放在我们的内存当中,内存对于cpu来讲,读取速度非常快。如果使用第二种方式,那么内存对于硬盘来讲,就是缓存空间。缓存也就是高速设备之于低速设备的一种称呼
- 使用链表来维护缓存空间(只是一种实现方式,但不是底层真正的实现方法)
- 当我们在取数据的时候,会在这个结构中查找,如果有这个数据,那么就叫缓存命中
- 如果取得数据没有在这个结构中,会将这个数据放在缓存链表的最后,然后淘汰掉最前面也就是最早放进缓存链表的数据,缓存链表大小依旧没变
- 缓存
开放思考
链表在前端应用(举例react的hook),各位大神可以在评论区讨论