概述
链表存储有序元素集合,但链表中的元素在内存中并不连续设置。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。
特点
- 相较于数组数据结构,链表的好处在于添加或移除元素的时候无需移动其他元素。
- 想访问链表中间的某个元素,需要从起点(表头)开始迭代链表直到找到所需元素。⭐️
- 链表最后一个节点的下一个元素始终是 undefined 或 null
操作
0. 循环迭代链表直到目标位置
getElementAt(index) {
if(index >= 0 && index <= this.count) { // 判断是否超出边界
let node = this.head
for(let i = 0; i < index && node != null; i++) {
node = node.next
}
return node
}
return undefined
}
1. 尾部添加元素
若链表为空,则直接添加第一个元素;
否则从头部开始循环访问链表,直到下一个元素为 undefined 或 null 时,让最后一个元素的指针指向插入元素
...
while(current.next != null) { // 若非最后一项则继续循环访问下一位
current = current.next
}
current.next = node // node 即需插入的元素
2. 从链表中移除元素
若删除头部,则将head设置为原头部指针指向的元素(第二个元素);
否则循环访问链表,将被删除元素前后的元素通过指针连接。
previous.next = current.next
3. 在任意位置插入元素
循环访问链表并找到插入位置,通过两个指针将前后元素及插入元素进行连接。
node.next = current
precious.next = node
4. 根据值返回元素位置
迭代节点并验证节点的元素和目标的元素是否相等。
5. 根据值删除指定元素
结合 4 和 2
双向链表
双向链表中链接是双向的,一个链向下一个元素,另一个链向上一个元素
1. 在任意位置插入新元素
node.next = current
precious.next = node
current.prev = node
node.prev = previous
2. 在任意位置移除元素
previous.next = current.next
current.next.prev = previous
循环链表
循环链表可以向链表一样只有单向引用,也可以像双向链表一样有双向引用。区别在于,最后一个元素指向下一个元素的指针(tail.next)不是引用 undefined,而是指向第一个元素(head)
即 head 元素的 tail.next 和指向 tail 元素的 head.prev
有序链表
是指保持元素有序的链表结构。除了使用排序算法之外,还可以将元素插入到正确的位置来保证链表的有序性。
链表逆序
假设当前有5个结点,head、a1、a2、a3、a4、a5,他们的头指针是head。我们的思路便是将a1作为当前元素一直往后遍历,并且将a1后面的数据依次挪到head之后。
链表相关算法题常见思路总结
- 改变位置通过画图理解指针变化
- 虚拟头节点
- 通过移动 指针 找到节点、区间,或者获取长度;也可以解决判断是否有环,是否相交等问题
- 通过 数组 Set 等数据结构 缓存 节点信息
- 对一些依赖于后面节点才可以完成的操作,可以通过 递归 解决问题
- 使用一些临时变量来存储next指针,以完成插入删除等操作
- 遇到需要使用目标节点前面的节点时,定义一个新的链表节点并使其next指针指向head节点
- 遇到需要改变头节点位置时,可以考虑使用环链表