list双向查找需用两个正向迭代器,分别从头和尾遍历;不能混用begin()与rbegin(),因类型不同;空容器需检查;splice可零拷贝移动节点。

list 的 begin() 和 rbegin() 不是同一类迭代器
想用 std::list 双向查找,不能直接拿 rbegin() 和 begin() 比较大小——它们类型不同:iterator 和 reverse_iterator 不能混用,编译直接报错 invalid operands to binary expression。
真正能双向遍历的写法,是用两个正向迭代器:一个从头走,一个从尾走。但注意,list 不支持随机访问,--end() 是合法的,end() - 1 是非法的。
-
auto it = lst.begin(); auto rit = lst.end(); --rit;—— 这才是安全的“尾迭代器”初始化方式 - 查找到中途相遇时,要判断
it == rit或it == ++rit(取决于是否允许重叠),因为 list 没有中间索引,只能靠迭代器相等性判断 - 别用
std::distance算位置,它对list是 O(n) 的,双向查找本意是早停,算距离反而拖慢
find() 只能单向,但可以自己写双向搜索循环
std::find 固定从 begin() 到 end(),没法指定方向。真要“更快找到”,得手动维护两个游标,尤其适合目标大概率靠近某端的场景(比如最近插入的元素常在尾部)。
下面这个模式能避免重复访问、提前退出:
立即学习“C++免费学习笔记(深入)”;
auto it = lst.begin();
auto rit = (lst.empty()) ? lst.end() : --lst.end();
while (it != rit && std::next(it) != rit) {
if (*it == target) return it;
if (*rit == target) return rit;
++it;
--rit;
}
// 处理奇数长度时中心节点
if (it == rit && *it == target) return it;- 循环条件用
it != rit && std::next(it) != rit,防止双指针交错跳过 - 末尾单独判一次
it == rit,因为奇数长度时两个指针会落在同一个节点上 - 别忘了空容器检查,
--lst.end()对空list是未定义行为
用 list::splice 移动节点比 erase+insert 更高效
双向查找常伴随“把命中项移到开头/结尾”的需求(比如 LRU)。有人写 erase(it); push_front(*it);,这会触发拷贝或移动构造,还可能使迭代器失效。
-
lst.splice(lst.begin(), lst, it);是零拷贝的节点指针重接,O(1),且it依然有效(只是位置变了) -
splice第二个参数必须是同类型list,不能传 vector 或临时 list - 如果目标迭代器来自另一个 list,要确保那个 list 没被销毁,否则
splice行为未定义
迭代器失效规则比 vector 严格得多
list 迭代器只在对应节点被 erase() 时失效;插入操作不影响其他迭代器。这点常被误用——比如一边查找一边删匹配项,却没意识到 ++it 在 erase(it) 后会解引用已释放节点。
- 安全删除所有匹配项的惯用写法:
it = lst.erase(it);(erase返回下一个有效迭代器) - 反向遍历时用
it = lst.erase(it); --it;要小心,空 list 或首节点删完后--it会到end(),不能再解引用 - 多线程下哪怕只读,也别假设
list迭代器“绝对稳定”——节点指针可能被其他线程splice调走,仅靠迭代器不保序
双向查找本身不难,难的是边界处理和后续操作的连贯性。最常出问题的不是算法逻辑,而是把 list 当 vector 用——比如取 size() 做循环上限(O(n))、或以为 rbegin() 能和 begin() 直接比较。











