二分查找必须基于有序数组,否则结果不可靠;C++标准库函数如std::binary_search隐式依赖升序,无序数组将导致错误结果;动态数据应改用std::set/map;降序需手动实现或传std::greater();循环实现比递归更安全高效;优先使用std::lower_bound而非手写;性能瓶颈常在于内存布局与缓存命中率而非算法本身。
这是最容易被忽略的前提。C++ 标准库的 std::binary_search、std::lower_bound 等函数都隐式依赖升序排列;若你传入无序数组,即使代码能跑通,返回值也毫无意义。实践中常见错误是:对原始数据只做了一次快排就以为“万事大吉”,却忽略了后续插入/修改后未重新排序,导致后续二分失效。
验证是否有序只需一次线性扫描:
bool is_sorted(const std::vector& arr) { for (size_t i = 1; i < arr.size(); ++i) if (arr[i] < arr[i-1]) return false; return true; }
std::set 或 std::map,它们内部维持红黑树,天然支持对数时间查找std::greater()
递归写法看似简洁,但在 C++ 中容易因深度过大触发栈溢出——尤其当数组长度接近 INT_MAX 时,递归调用栈可能达数十万层。而循环版本空间复杂度稳定为 O(1),且现代编译器对其优化更充分(如循环展开、条件预测)。
标准循环实现(查找目标值是否存在):
bool binary_search_iterative(const std::vector& arr, int target) { int left = 0, right = static_cast (arr.size()) - 1; while (left <= right) { int mid = left + (right - left) / 2; // 防止 left+right 溢出 if (arr[ mid] == target) return true; else if (arr[mid] < target) left = mid + 1; else right = mid - 1; } return false; }
mid = left + (right - left) / 2 是必须写法,(left + right) / 2 在 left 和 right 均为大正数时会整型溢出right 初始化为 arr.size() - 1,不是 arr.size();否则越界访问,不是 ,否则单元素数组会漏判
lower_bound 比手写循环更值得优先使用除非你在写算法题或教学演示,否则不建议自己重写二分逻辑。C++ 标准库的 std::lower_bound 经过高度优化,支持任意随机访问迭代器,且对缓存友好(连续内存访问模式)。它返回第一个不小于 target 的位置,配合 != arr.end() 和 == target 即可完成存在性判断。
auto it = std::lower_bound(arr.begin(), arr.end(), target); bool found = (it != arr.end() && *it == target);
std::distance(arr.begin(), it),不要用 it - arr.begin()(虽等价但可读性差)std::vector 上,lower_bound 实测比手写循环快 5%~10%,主要受益于底层 SIMD 指令和分支预测优化RandomAccessIterator,对 std::list 无效——此时应换容器,而非强行二分理论时间复杂度都是 O(log n),但实际性能差距可能达 3 倍以上。关键变量不是循环还是递归,而是 CPU 缓存命中率。例如:一个 1GB 的 std::vector,即使二分只访问 ~30 个元素,但如果这些元素分散在不同内存页,每次访问都触发缺页中断,速度会暴跌。
std::vector 天然满足,std::deque 不满足)push_back 或 erase,这会导致内存重分配和迭代器失效mmap 将数组映射为只读内存,减少内核态拷贝真正卡住性能的,往往不是二分本身,而是你没意识到:二分前的数据加载、排序、内存布局,已经决定了它的上限。