跳转到主内容
极星编程网:以代码为星,赴技术山海!

C++实现LRU缓存Linit控制 _ 双向链表与哈希映射组合【源码】

std::list不能直接替代自定义双向链表,因其不支持通过值快速定位迭代器,需额外维护指针或迭代器映射,存在失效风险;而手写链表配合哈希表存裸指针可彻底避免迭代器失效与内存重分配问题。 为什么
std::list
不能直接替代自定义双向链表 因为
std::list::erase(iterator)
是 O(1),但你得先拿到那个
iterator
;而查找 key 对应的节点在
std::list
中是 O(N) —— 哈希表存的不能只是值,必须能反查到迭代器。标准容器不支持“通过值快速定位迭代器”,所以必须自己维护节点指针或用
std::list
的
splice
配合哈希表存
std::list::iterator
,但后者在节点被
erase
后迭代器会失效,且 C++11 后
std::list
迭代器可拷贝,实际可行,但有隐含风险:
iterator
持有时长必须严格与节点生命周期一致。 更稳妥的做法是手写双向链表节点: 每个节点含
key
、
value
、
prev
、
next
哈希表(
std::unordered_map
)直接存指向节点的裸指针 所有增删只操作指针,无内存重分配干扰,无迭代器失效问题
get()
和
put()
中如何避免重复逻辑 核心是把“把某节点移到 head”抽成独立函数,否则两处都要写一遍解链 + 插 head,极易漏掉某个指针赋值。尤其注意边界:节点本身就是 head 或 tail 时,
prev
/
next
可能为
nullptr
,直接解链会崩溃。 实操建议: 立即学习 “ C++免费学习笔记(深入) ”; 统一用辅助函数
moveToHead(Node* node)
,内部先
removeNode(node)
,再
addToHead(node)
removeNode()
要判空:
if (node->prev) node->prev->next = node->next;
,同理处理
next
addToHead()
时记得更新
head->next->prev = node
和
node->next = head->next
,顺序错会导致链断裂 容量超限时,
tail
节点删除的正确顺序 删 tail 不是简单
delete tail
就完事。它涉及三件事:从哈希表移除 key、从链表解链、释放内存。顺序错了就会访问野指针或泄漏。 C知道 CSDN推出的一款AI技术问答工具 下载 必须按这个顺序执行: 从
std::unordered_map
中用
erase(tail->key)
先摘除映射 调用
removeNode(tail)
把它从链表中摘掉(此时 tail 仍有效,可取
key
) 最后
delete tail
;删完立刻让
tail = tail->prev
(注意:删的是旧 tail,新 tail 是它的前一个) 如果先
delete tail
再去
erase(map[tail->key])
,就是未定义行为。 构造函数里初始化 head/tail 哨兵节点的必要性 不用哨兵也能做,但每处插入/删除都要判空:
if (!head) { head = node; tail = node; }
,代码膨胀且易错。用两个固定哨兵(
head
和
tail
永远不存真实数据),所有操作都变成“中间插入”或“中间删除”,逻辑高度一致。 关键细节: 构造时
head->next = tail
,
tail->prev = head
,其余字段置
nullptr
真实数据节点永远插在
head
和
tail
之间,所以
get()
后
moveToHead()
时,不用管 head 是否为空 容量检查只需比对
size_
和
capacity
,无需遍历链表算长度 哨兵不是炫技,是把边界 case 全部吸收掉——少一个
if
,就少一个 bug 温床。

相关文章