C++怎么实现二分查找 C++标准库binary_search【精析】

5次阅读

binary_search返回bool因其仅判断存在性而不提供位置信息;需下标时应使用std::lower_bound并检查是否等于end()。

C++怎么实现二分查找 C++标准库binary_search【精析】

binary_search 为什么返回 bool 而不是下标

因为它的设计目标只有一个:查“有没有”。它不关心在哪,只回答“存在吗”。这和 std::lower_boundstd::upper_bound 的定位完全不同——后两者才负责定位。

常见错误现象:binary_search 返回 true,但你接着去访问 v[0] 或硬算下标,结果越界或逻辑错乱。它根本不提供位置信息。

  • 如果你需要下标,直接用 std::lower_bound,然后检查是否等于 end()
  • 如果容器是 std::vector 且已排序,binary_search(v.begin(), v.end(), x) 是安全的;但若未排序,结果未定义(不是报错,而是可能返回任意 bool)
  • 注意:迭代器范围必须满足严格弱序,比如自定义比较函数时,comp(a, a) 必须为 false,否则行为未定义

传入自定义比较函数时最容易漏掉的条件

binary_search结构体或自定义类型时,很多人只写 comp(a, b),却忘了它必须和排序时用的是**同一个函数对象**。

错误示例:用 std::sort(v.begin(), v.end(), [](auto& x, auto& y) { return x.id 排序,但调用 <code>binary_search 时漏传比较函数,或传了另一个逻辑(比如按 name 比),结果永远返回 false —— 因为底层二分依赖的序关系和实际数据排列不一致。

立即学习C++免费学习笔记(深入)”;

  • 务必配对使用:排序用什么比较器,查找也得传什么比较器
  • Lambda 捕获变量需注意生命周期:如果比较函数捕获了局部变量,而容器生命周期更长,运行时可能读到野值
  • 函数对象类型要一致:不能一个用 std::less,另一个用手写 lambda,即使逻辑相同,类型不同也会导致模板实例化失败

在 std::set / std::map 上调用 binary_search 是多此一举

std::setstd::map 底层是红黑树,自带 find() 成员函数,平均 O(log n),且返回迭代器,既能判断存在性又能取值。此时用全局 binary_search 不仅冗余,还容易出错。

典型误用:binary_search(s.begin(), s.end(), x) —— 看似可行,但 std::set::iteratorconst 迭代器,某些标准库实现会因类型推导问题编译失败;更重要的是,它绕过了容器自己的优化路径。

  • std::set,直接用 s.find(x) != s.end()
  • std::map,用 m.find(key) != m.end() 或更简洁的 m.count(key)(注意 count 对 map 永远是 0 或 1)
  • 只有面对原始数组、std::vectorstd::Array 这类“无查找能力”的序列时,binary_search 才是合理选择

性能陷阱:迭代器类型影响是否真走二分

binary_search 要求随机访问迭代器(RandomAccessIterator)才能达到 O(log n)。如果误传了 std::list::iteratorstd::forward_list::iterator,代码仍可能编译通过(取决于标准库实现),但内部会退化成线性遍历 —— 完全失去二分意义。

错误信号:明明数据量上万,查找却慢得反常,且 CPU 占用集中在循环判断上。这不是算法问题,是迭代器类型没达标。

  • 安全组合:只对 std::vectorstd::dequestd::array、原生指针(如 &arr[0])使用 binary_search
  • 不确定时,用 std::is_same_v<typename it::iterator_category std::random_access_iterator_tag></typename> 静态断言(c++20 可用 std::random_access_iterator 概念)
  • 别试图对 std::vector<t>::const_iterator</t> 做手脚来“适配”非随机访问容器——没用,类型不匹配就是不匹配

最常被忽略的一点:binary_search 不检查输入范围是否真有序。它假设你已经排好序,连 warning 都不会给。一旦数据没排,返回值就纯粹是垃圾,而且你还很难复现——有时候碰巧 true,有时候 false,调试起来像玄学。

text=ZqhQzanResources