「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」
单向链表
百度百科:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
简单来说就是,链表中的每一个元素在内存中都不是连续存储的。其中每一个元素有一个存储本身的节点,还有另外一个指向下一个元素的引用(指针/next),这些元素形成了一个链。
单链表的插入与删除节点,由于链表中每个元素都不是连续存储的,需要提前找到下一个需要连接的节点,如果直接断开两个链,则将失去引用
链表操作代码演示
如何生成链表
// 构造函数
function NodeList(val, next) {
this.val = val ? val : 0
this.next = next ? next : null
}
// 生成链表
function generateList(array) {
let ret = new NodeList() //虚拟头节点,方便返回
let cur = ret
for (let i = 0; i < array.length; i++) {
cur.next = new NodeList(array[i])
cur = cur.next
}
return ret.next
}
const arr = [1, 2, 3, 4, 5]
const list = generateList(arr) // 1=>2=>3=>4=>5
插入节点
/**
* @description: 插入节点
* @param {Number} index
* @param {Number|String} val
*/
function insertNode(index, val){
let head = list
while(head.val !== index) { //找到节点
head = head.next
}
const node = new NodeList(val) //新增待插入节点
node.next = head.next //x节点先连上下一个节点
head.next = node //从3的位置断开连上x节点
}
//在3之后插入x节点
insertNode(3, 'x') // list: 1=>2=>3=>x=>4=>5
删除节点
/**
* @description: 删除节点
* @param {Number|String}
*/
function deleteNode(val){
let head = new NodeList()
head.next = list
while(head.next && head.next.val !== val) { //找到待删除节点的前一个节点
head = head.next
}
head.next = head.next.next ? head.next.next : null
}
// 删除节点3
deleteNode(3) // list: 1=>2=>x=>4=>5
相关算法题 707. 设计链表
循环(环形)链表
循环列表是一种特殊的单链表,它跟单链表唯一的区别就在于它的尾结点又指回了链表的头结点,首尾相连,形成了一个环,所以叫做循环链表。
相关算法题 141. 环形链表
解题思路:利用双指针,一开始都指向链表头部。一个指针每次向前走一步,另一个指针每次向前走两步,如果在某个点两个指针相遇了则说明链表中存在环,否则如果当快指针先走到链表尾部,则说明不存在环。
/**
* @param {ListNode} head
* @return {boolean}
*/
var hasCycle = function(head) {
if(!head || !head.next) return false
var p = q = head
while(q && q.next) { // 边界条件,上一次循环结束后,快指针可能已经在链表尾部
p = p.next
q = q.next.next
if(p === q) return true
}
return false
};
双向链表
与单链表相比,储存同样的数据,双向链表会占用更多的内存空间。虽然多占用了空间,但是双向链表在处理根据已知结点查找上一节点、有序链表查找等问题上,都表现的更灵活高效。
经典链表算法题
206. 反转链表
25. K 个一组翻转链表
138. 复制带随机指针的链表
链表的应用:LRU缓存淘汰算法
LRU:Least Recently Used,最近最少使用策略
实现思路
维护一个链表,越靠近链表尾部,表示越早访问的数据。
当有一个新数据被访问,遍历链表
- 如果该数据在链表中,则将其从原来的位置删除,然后放到链表头部
- 如果这个数据不在链表中,则直接将其添加到链表头部,此时如果缓存已满,则需要删除链表的尾结点
感兴趣的同学可以自己动手实现一下
-- end --