二分查找算法原理与C++实现详解
1. 二分查找算法基础概念二分查找Binary Search是一种在有序数组中查找特定元素的搜索算法。它的工作原理是通过不断将搜索范围减半来快速定位目标值这种分而治之的策略使其时间复杂度达到O(log n)远优于线性查找的O(n)。我第一次接触二分查找是在大学的数据结构课上当时教授用一个简单的例子说明了它的威力在一个包含100万个元素的排序数组中查找某个值线性查找最多需要100万次比较而二分查找最多只需要20次这个直观的对比让我立刻理解了算法效率的重要性。二分查找有三个基本前提条件数据结构必须是数组链表不行因为无法随机访问数组必须是有序的升序或降序数组元素必须能够进行比较操作注意在实际项目中如果数组经常变动频繁插入/删除二分查找可能不是最佳选择因为维护有序数组的成本可能抵消查找效率的优势。2. C实现二分查找的标准写法2.1 迭代法实现下面是一个标准的C迭代实现版本我习惯使用左闭右开区间[left, right)的写法这种边界处理方式在实践中更不容易出错int binarySearch(const vectorint nums, int target) { int left 0; int right nums.size(); // 注意右边界是开区间 while (left right) { // 注意循环条件 int mid left (right - left) / 2; // 防止溢出 if (nums[mid] target) { return mid; } else if (nums[mid] target) { left mid 1; // 调整左边界 } else { right mid; // 调整右边界 } } return -1; // 未找到 }这个实现有几个关键点值得注意使用left (right - left)/2而不是(left right)/2来计算mid可以避免整数溢出循环条件是left right而不是left right因为我们使用的是右开区间当nums[mid] target时调整左边界为mid 1而不是mid这样可以确保每次迭代都能缩小搜索范围2.2 递归法实现虽然迭代版本更常用但递归实现也能帮助我们更好地理解算法逻辑int binarySearchRecursive(const vectorint nums, int target, int left, int right) { if (left right) { return -1; } int mid left (right - left) / 2; if (nums[mid] target) { return mid; } else if (nums[mid] target) { return binarySearchRecursive(nums, target, mid 1, right); } else { return binarySearchRecursive(nums, target, left, mid); } }递归版本虽然简洁但在实际项目中我通常避免使用因为递归调用有额外的函数调用开销对于大数组可能导致栈溢出调试起来不如迭代版本直观3. 二分查找的变体与应用场景3.1 查找第一个/最后一个匹配项标准二分查找只能找到一个匹配项但实际需求往往更复杂。比如在[1,2,2,2,3]中查找2我们可能需要第一个或最后一个2的位置。这是我工作中经常遇到的变体// 查找第一个等于target的元素 int findFirst(const vectorint nums, int target) { int left 0; int right nums.size(); int result -1; while (left right) { int mid left (right - left) / 2; if (nums[mid] target) { right mid; if (nums[mid] target) result mid; } else { left mid 1; } } return result; } // 查找最后一个等于target的元素 int findLast(const vectorint nums, int target) { int left 0; int right nums.size(); int result -1; while (left right) { int mid left (right - left) / 2; if (nums[mid] target) { left mid 1; if (nums[mid] target) result mid; } else { right mid; } } return result; }3.2 查找插入位置另一个常见变体是查找目标值应该插入的位置即使目标值不存在于数组中。这在实现类似std::lower_bound的功能时非常有用int searchInsert(const vectorint nums, int target) { int left 0; int right nums.size(); while (left right) { int mid left (right - left) / 2; if (nums[mid] target) { left mid 1; } else { right mid; } } return left; }这个变体在实现有序集合操作时特别有用比如维护一个动态的有序列表。4. 二分查找的边界条件与调试技巧4.1 常见错误与陷阱即使是有经验的程序员在实现二分查找时也容易犯一些错误。以下是我在代码审查中经常发现的问题整数溢出使用(left right)/2计算mid可能导致溢出。正确的做法是left (right - left)/2。边界条件处理不当循环条件是left right还是left right这取决于你使用的是闭区间还是开区间。我建议始终采用一种风格并保持一致。更新边界错误当nums[mid] target时应该更新left mid 1而不是left mid否则可能导致无限循环。未排序输入忘记验证输入是否已排序导致查找结果错误。4.2 调试技巧当二分查找出现问题时我通常会采用以下调试方法打印日志在循环内部打印left、right和mid的值观察搜索范围的变化。while (left right) { int mid left (right - left) / 2; cout left left , right right , mid mid endl; // ... }单元测试编写测试用例覆盖各种边界情况空数组单元素数组目标值在数组开头/结尾目标值不存在有重复元素的数组可视化调试对于复杂问题我有时会在纸上画出数组和搜索范围的变化过程。5. 二分查找的性能优化5.1 循环展开对于性能关键的场景可以考虑手动展开循环来减少分支预测错误int binarySearchUnrolled(const vectorint nums, int target) { int left 0; int right nums.size(); while (right - left 4) { int mid left (right - left) / 2; if (nums[mid] target) { left mid 1; } else { right mid; } } // 处理剩余的小范围 for (int i left; i right; i) { if (nums[i] target) { return i; } } return -1; }5.2 缓存友好的实现现代CPU的缓存机制对二分查找的性能有很大影响。对于非常大的数组可以考虑以下优化预取在比较当前mid元素时预取下一个可能访问的内存位置块存储将数组分成多个块先在块级别进行二分查找再在块内线性查找5.3 使用STL的实现C标准库提供了std::binary_search、std::lower_bound和std::upper_bound等算法它们通常经过高度优化#include algorithm void stlExample() { vectorint nums {1, 2, 3, 4, 5}; // 检查元素是否存在 bool exists binary_search(nums.begin(), nums.end(), 3); // 查找第一个不小于3的元素 auto it lower_bound(nums.begin(), nums.end(), 3); // 查找第一个大于3的元素 auto it2 upper_bound(nums.begin(), nums.end(), 3); }在实际项目中我通常优先使用STL的实现除非有特殊需求。6. 二分查找在实际项目中的应用6.1 游戏开发中的二分查找在游戏开发中我经常用二分查找来解决各种问题。比如敌人生成系统根据玩家等级在预定义的难度曲线中查找合适的敌人配置动画关键帧查找在时间轴上快速定位当前应该播放的动画帧碰撞检测优化在空间分区数据结构中快速定位可能发生碰撞的对象6.2 金融领域的应用在量化金融系统中二分查找被广泛用于时间序列查询在大量历史数据中快速定位特定时间点的价格订单簿匹配在有序的买卖订单中查找最佳匹配价格风险计算在预计算的风险值表中快速查找对应值6.3 机器学习中的使用虽然现代机器学习框架提供了高级API但理解底层算法仍然很重要超参数调优在参数搜索空间中使用二分查找快速定位最优组合决策树分裂在特征值中寻找最佳分割点神经网络量化在权重分布中查找合适的量化阈值7. 二分查找与其他搜索算法的比较7.1 与线性查找的比较特性二分查找线性查找时间复杂度O(log n)O(n)空间复杂度O(1)O(1)前提条件必须有序无要求适用数据结构数组/随机访问任何序列缓存友好性较差较好7.2 与哈希表的比较虽然哈希表的查找时间是O(1)但二分查找仍有其优势有序性二分查找可以轻松支持范围查询和有序遍历内存效率不需要额外的哈希表结构稳定性哈希表可能因冲突而性能下降实现简单不需要处理哈希函数和冲突解决7.3 与树形结构的比较平衡二叉搜索树如AVL树、红黑树的查找性能也是O(log n)但实现复杂度二分查找更简单内存局部性数组形式的二分查找对缓存更友好更新成本维护有序数组的成本高于树结构的插入/删除8. 进阶话题在非传统场景中的应用8.1 在无限流中查找对于理论上无限但有序的数据流我们可以使用指数搜索Exponential Search结合二分查找先以指数速度1,2,4,8,...扩大搜索范围当确定范围后再进行标准的二分查找int exponentialSearch(InputStream stream, int target) { int bound 1; while (stream.has(bound) stream.get(bound) target) { bound * 2; } return binarySearchInStream(stream, target, bound/2, min(bound, stream.size())); }8.2 在旋转排序数组中查找这是一个经典的面试题在类似[4,5,6,7,0,1,2]的旋转数组中查找目标值。解决方案需要修改标准的二分查找int searchInRotatedArray(const vectorint nums, int target) { int left 0; int right nums.size(); while (left right) { int mid left (right - left) / 2; if (nums[mid] target) { return mid; } // 判断哪一部分是有序的 if (nums[left] nums[mid]) { // 左半部分有序 if (nums[left] target target nums[mid]) { right mid; } else { left mid 1; } } else { // 右半部分有序 if (nums[mid] target target nums[right-1]) { left mid 1; } else { right mid; } } } return -1; }8.3 在二维矩阵中查找对于行列都有序的二维矩阵可以使用一种特殊的阶梯搜索算法bool searchMatrix(const vectorvectorint matrix, int target) { if (matrix.empty()) return false; int row 0; int col matrix[0].size() - 1; while (row matrix.size() col 0) { if (matrix[row][col] target) { return true; } else if (matrix[row][col] target) { row; } else { col--; } } return false; }这种算法的时间复杂度是O(mn)其中m和n分别是矩阵的行数和列数。9. 现代C中的二分查找9.1 使用模板实现通用版本我们可以使用C模板来实现支持任意可比较类型的二分查找template typename T, typename Compare lessT int binarySearchTemplate(const vectorT vec, const T target, Compare comp Compare()) { int left 0; int right vec.size(); while (left right) { int mid left (right - left) / 2; if (vec[mid] target) { return mid; } else if (comp(vec[mid], target)) { left mid 1; } else { right mid; } } return -1; }这个版本可以用于任何定义了比较操作的类型甚至可以通过传入自定义比较函数来支持特殊比较逻辑。9.2 并行化二分查找对于非常大的数组可以考虑并行化二分查找。基本思路是将数组分成多个段在各段中并行搜索#include execution int parallelBinarySearch(const vectorint nums, int target) { const int chunk_size 1000; // 每个块的大小 const int num_chunks (nums.size() chunk_size - 1) / chunk_size; vectorint results(num_chunks, -1); // 并行处理每个块 for_each(execution::par, counting_iterator(0), counting_iterator(num_chunks), [](int i) { int start i * chunk_size; int end min(start chunk_size, static_castint(nums.size())); if (nums[start] target target nums[end-1]) { // 在这个块内进行二分查找 auto it lower_bound(nums.begin()start, nums.begin()end, target); if (it ! nums.begin()end *it target) { results[i] distance(nums.begin(), it); } } }); // 检查是否有找到 for (int pos : results) { if (pos ! -1) { return pos; } } return -1; }9.3 使用C20 rangesC20引入了ranges库可以写出更简洁的二分查找代码#include ranges #include algorithm int binarySearchRanges(const vectorint nums, int target) { auto subrange std::ranges::equal_range(nums, target); if (subrange.begin() ! subrange.end()) { return distance(nums.begin(), subrange.begin()); } return -1; }10. 二分查找的教学与学习建议10.1 如何教授二分查找在教授二分查找时我通常会采用以下步骤从直观例子开始使用电话号码簿或字典查找的例子说明分而治之的概念强调前提条件明确必须是有序数组可视化过程在黑板或纸上画出数组和搜索范围的变化边界条件讨论专门讨论各种边界情况空数组、单元素、目标不存在等错误实现分析展示常见错误实现并讨论为什么出错10.2 学习二分查找的建议对于学习者我的建议是理解而非记忆理解算法为什么有效而不仅仅是记住代码多种实现方式尝试写迭代版、递归版、各种变体大量练习在LeetCode等平台练习相关题目调试实践故意写错实现然后通过调试找出问题性能分析对不同实现进行性能测试和比较10.3 常见面试问题准备在准备技术面试时应该熟悉以下类型的二分查找问题标准二分查找实现查找第一个/最后一个匹配项旋转排序数组中的查找在未知大小的排序数组中查找寻找峰值元素在二维矩阵中查找寻找重复数计算平方根我在面试候选人时通常会从标准实现开始然后逐步增加难度观察候选人如何处理边界条件和算法变体。

相关新闻

CAD_Sketcher:当参数化几何遇见艺术创作的自由边界

CAD_Sketcher:当参数化几何遇见艺术创作的自由边界

CAD_Sketcher:当参数化几何遇见艺术创作的自由边界 【免费下载链接】CAD_Sketcher Constraint-based geometry sketcher for blender 项目地址: https://gitcode.com/gh_mirrors/ca/CAD_Sketcher 在三维建模的世界里,精确与自由常常站在对立的两端…

2026/10/15 10:39:04 阅读更多 →
Video Download Helper:终极免费开源视频下载助手完整指南

Video Download Helper:终极免费开源视频下载助手完整指南

Video Download Helper:终极免费开源视频下载助手完整指南 【免费下载链接】VideoDownloadHelper Chrome Extension to Help Download Video for Some Video Sites. 项目地址: https://gitcode.com/gh_mirrors/vi/VideoDownloadHelper 你是否曾经在观看精彩的…

2026/10/10 12:05:55 阅读更多 →
TranslucentTB终极安装指南:5步解决Windows任务栏透明化工具常见错误

TranslucentTB终极安装指南:5步解决Windows任务栏透明化工具常见错误

TranslucentTB终极安装指南:5步解决Windows任务栏透明化工具常见错误 【免费下载链接】TranslucentTB A lightweight utility that makes the Windows taskbar translucent/transparent. 项目地址: https://gitcode.com/gh_mirrors/tr/TranslucentTB Translu…

2026/10/7 9:10:02 阅读更多 →

最新新闻

SpringBoot+Vue医院预约挂号系统设计与优化

SpringBoot+Vue医院预约挂号系统设计与优化

1. 项目概述:现代化医疗预约解决方案这个基于SpringBootVue的医院预约挂号系统,是我去年为某三甲医院信息化改造设计的核心模块。传统医疗机构的预约挂号流程普遍存在页面卡顿、数据不同步、操作繁琐等问题,而采用前后端分离架构后&#xff0…

2026/10/15 10:39:41 阅读更多 →
微信小游戏是什么?运行原理与开发入门全解析

微信小游戏是什么?运行原理与开发入门全解析

做前端时间长了,总有人问我:微信小游戏到底是个什么东西?能像网页一样不用下载就玩,又能做出挺复杂的游戏,还能在朋友圈里被疯转。说实话,我一开始也绕了不少弯路,总觉得它像小程序,…

2026/10/15 10:39:41 阅读更多 →
超帧在SLAM中的作用:从关键帧组织到局部优化的全面解析

超帧在SLAM中的作用:从关键帧组织到局部优化的全面解析

1. 为什么我会盯上 Hyperframes 这个概念做了这么多年SLAM相关的工作,我逐渐意识到一个残酷的事实:绝大多数做视觉SLAM的人,对关键帧的认知停留在“选一帧长得不一样的图”这个层面,更别说超帧(Hyperframes&#xff09…

2026/10/15 10:39:41 阅读更多 →
D-S证据理论:用不确定性推理解决急诊分诊与医疗AI评估难题

D-S证据理论:用不确定性推理解决急诊分诊与医疗AI评估难题

急诊分诊这件事,表面上是个分诊护士按个红黄绿标签的过程,实际上是个高压、高噪声、高代价的决策现场。病患到急诊的那一刻,能采集到的信息往往是零散的:病人自己的口述、家属慌张的补充、监护仪上跳动的几个数字、既往病历里一段…

2026/10/15 10:39:41 阅读更多 →
superpowers安装全攻略:从环境准备到排错重装的一条龙实操

superpowers安装全攻略:从环境准备到排错重装的一条龙实操

“superpowers”这个词最近在我几个技术交流群里出现的频率高得离谱,不少人在问“想要安装superpowers”,还有人把它列进新机器必备清单。我最初以为是什么夸张的营销宣传,直到自己在全新的机器上完整走了一遍安装、配置、排错的全流程&#…

2026/10/15 10:39:41 阅读更多 →
学Simulink——储能变流器(PCS)中 PWM 整流器的恒流充放电(CC Charge / CC Discharge)仿真

学Simulink——储能变流器(PCS)中 PWM 整流器的恒流充放电(CC Charge / CC Discharge)仿真

目录 手把手教你学Simulink——储能变流器(PCS)中 PWM 整流器的恒流充放电(CC Charge / CC Discharge)仿真 一、为什么储能 PCS 要做 恒流(CC)充放电双向 PWM 整流器 二、PCS 双向控制与 CC 模式原理** 2.1 功率平衡估算(用于 idref​折算) 2.2 模式定义 三、关键…

2026/10/15 10:38:32 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →