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