当前位置: 首页 > news >正文

查找---二分查找

一、算法核心概念

二分查找(Binary Search)又称折半查找,是一种针对有序数组的高效查找算法。其核心思想是通过不断将查找范围减半,快速缩小目标元素的可能位置,最终实现O(log n)的时间复杂度。与线性查找(逐个遍历,O(n)复杂度)相比,二分查找在数据量较大时效率提升极其显著。

二、适用条件

二分查找的应用有严格前提:

  1. 数组必须有序(升序或降序,本文以升序为例);
  2. 支持随机访问(如数组结构,可通过索引直接访问元素,链表不适用);
  3. 数据静态或修改频率低(若需频繁插入/删除,维护有序性成本高)。
三、算法原理

假设存在升序数组arr,目标值为target,查找步骤如下:

  1. 定义查找区间的左右边界:low = 0(起始索引),high = n-1(末尾索引,n为数组长度);
  2. 计算区间中点:mid = low + (high - low) / 2(避免low + high溢出);
  3. 比较arr[mid]target
    • arr[mid] == target:找到目标,返回mid
    • arr[mid] > target:目标在左半区间,更新high = mid - 1
    • arr[mid] < target:目标在右半区间,更新low = mid + 1
  4. 重复步骤2-3,直到low > high(区间为空,目标不存在),返回-1
四、时间与空间复杂度
  • 时间复杂度:每次查找范围减半,最多需要log₂n次操作,故为O(log n)。例如,n=10⁶时,最多仅需20次查找(2²⁰≈10⁶)。
  • 空间复杂度
    • 非递归实现:仅使用常数额外空间,O(1)
    • 递归实现:递归调用栈深度为log₂n,O(log n)
五、C/C++实现示例
1. 基础版(非递归)

功能:在升序数组中查找目标值,返回索引(不存在则返回-1)。

#include <iostream>
#include <vector>
using namespace std;// 非递归实现二分查找
int binarySearch(const vector<int>& arr, int target) {int low = 0;int high = arr.size() - 1;while (low <= high) {// 计算中点(避免low + high溢出)int mid = low + (high - low) / 2;if (arr[mid] == target) {return mid; // 找到目标,返回索引} else if (arr[mid] > target) {high = mid - 1; // 目标在左半区间} else {low = mid + 1; // 目标在右半区间}}return -1; // 目标不存在
}int main() {vector<int> arr = {1, 3, 5, 7, 9, 11, 13};int target = 7;int index = binarySearch(arr, target);if (index != -1) {cout << "目标 " << target << " 位于索引 " << index << endl;} else {cout << "目标 " << target << " 不存在" << endl;}return 0;
}

关键细节

  • 中点计算用low + (high - low)/2而非(low + high)/2,避免low + high超过int最大值导致溢出;
  • 循环条件为low <= high(当low == high时,区间仍有一个元素需检查);
  • 边界更新用mid ± 1(排除已检查的mid位置,避免死循环)。
2. 递归版

功能与基础版一致,通过函数递归实现查找。

#include <iostream>
#include <vector>
using namespace std;// 递归实现二分查找
int binarySearchRecursive(const vector<int>& arr, int target, int low, int high) {// 递归终止条件:区间为空if (low > high) {return -1;}int mid = low + (high - low) / 2;if (arr[mid] == target) {return mid;} else if (arr[mid] > target) {// 递归查找左半区间return binarySearchRecursive(arr, target, low, mid - 1);} else {// 递归查找右半区间return binarySearchRecursive(arr, target, mid + 1, high);}
}int main() {vector<int> arr = {2, 4, 6, 8, 10};int target = 6;int index = binarySearchRecursive(arr, target, 0, arr.size() - 1);if (index != -1) {cout << "目标 " << target << " 位于索引 " << index << endl;} else {cout << "目标 " << target << " 不存在" << endl;}return 0;
}

特点:代码简洁但递归调用有额外栈空间开销,数据量极大时可能栈溢出,实际中更推荐非递归版本。

3. 变种:查找重复元素的第一个/最后一个位置

当数组存在重复元素时(如[1, 2, 2, 2, 3]),基础版可能返回任意一个匹配位置。若需查找第一个或最后一个目标元素,需调整边界更新逻辑。

示例:查找第一个等于target的元素

#include <iostream>
#include <vector>
using namespace std;// 查找第一个等于target的元素索引
int findFirstOccurrence(const vector<int>& arr, int target) {int low = 0;int high = arr.size() - 1;int result = -1; // 记录结果while (low <= high) {int mid = low + (high - low) / 2;if (arr[mid] == target) {result = mid; // 暂存当前位置high = mid - 1; // 继续向左查找更早的位置} else if (arr[mid] > target) {high = mid - 1;} else {low = mid + 1;}}return result;
}// 查找最后一个等于target的元素索引
int findLastOccurrence(const vector<int>& arr, int target) {int low = 0;int high = arr.size() - 1;int result = -1;while (low <= high) {int mid = low + (high - low) / 2;if (arr[mid] == target) {result = mid; // 暂存当前位置low = mid + 1; // 继续向右查找更晚的位置} else if (arr[mid] > target) {high = mid - 1;} else {low = mid + 1;}}return result;
}int main() {vector<int> arr = {1, 2, 2, 2, 3, 4};int target = 2;int first = findFirstOccurrence(arr, target);int last = findLastOccurrence(arr, target);cout << "第一个 " << target << " 位于索引 " << first << endl; // 输出1cout << "最后一个 " << target << " 位于索引 " << last << endl; // 输出3return 0;
}

逻辑调整

  • 找到匹配元素时不立即返回,而是继续向目标方向(左/右)收缩区间,直到区间为空,最终记录的位置即为第一个/最后一个匹配项。
六、常见错误与边界处理
  1. 死循环:若边界更新时未排除mid(如high = mid而非high = mid - 1),可能导致lowhigh无法收敛(例如low=high=mid时,循环永不结束)。
  2. 溢出问题:中点计算若用(low + high)/2,当lowhigh均接近int最大值时,low + high会溢出为负数,导致mid计算错误。
  3. 空数组处理:需提前判断数组长度为0的情况,直接返回-1。
  4. 目标值超出数组范围:若target < arr[0]target > arr[n-1],应快速返回-1,减少无效循环。
七、应用场景
  1. 字典查询:如通讯录按姓名首字母排序后查找联系人;
  2. 数值逼近:如求一个数的平方根(整数部分)、寻找函数零点;
  3. 数据库索引:数据库中B+树索引的查找逻辑基于二分思想;
  4. 日志分析:在有序日志(如按时间排序)中定位特定时间点的记录。

二分查找是一种“以空间换时间”的经典算法,其高效性依赖于有序数据和随机访问特性。实际应用中需注意边界处理和重复元素场景,根据需求选择基础版或变种实现。


文章转载自:

http://zKeWG9bE.nrLhr.cn
http://EHBhIzc8.nrLhr.cn
http://vWiioROv.nrLhr.cn
http://Be56Y2wA.nrLhr.cn
http://faU9XLKx.nrLhr.cn
http://yHKF3kU3.nrLhr.cn
http://XeF5LLuz.nrLhr.cn
http://2iSmFYi4.nrLhr.cn
http://NL4Ywr7m.nrLhr.cn
http://ZBPCyZ78.nrLhr.cn
http://5S5bltCL.nrLhr.cn
http://KWaUwK6y.nrLhr.cn
http://0b0NQycp.nrLhr.cn
http://w5kYeKgu.nrLhr.cn
http://zw4M7KnV.nrLhr.cn
http://lVz5mc8p.nrLhr.cn
http://tFSXSIKl.nrLhr.cn
http://bND5kZuW.nrLhr.cn
http://Vx8neQsd.nrLhr.cn
http://ERrWtd7U.nrLhr.cn
http://dXYBzXDL.nrLhr.cn
http://YKwBvZW1.nrLhr.cn
http://5y3kzzRq.nrLhr.cn
http://iApqfQCE.nrLhr.cn
http://71a7cxui.nrLhr.cn
http://FUAp95Wp.nrLhr.cn
http://BAL1Llfv.nrLhr.cn
http://Z7Eufz0B.nrLhr.cn
http://eFNiBQCe.nrLhr.cn
http://Tw2qC0NL.nrLhr.cn
http://www.dtcms.com/a/373964.html

相关文章:

  • 求解二次方程
  • ArcGISPro应用指南:使用ArcGIS Pro制作弧线OD图
  • ZYNQ EMMC
  • uni-app头像叠加显示
  • 链改 2.0 六方共识深圳发布 ——“可信资产 IPO + 数链金融 RWA” 双轮驱动
  • ARM -- 汇编语言
  • HTML和CSS学习
  • 深度解析:IService 与 ServiceImpl 的区别
  • STM32 - Embedded IDE - GCC - rt_thread_nano的终端msh>不工作的排查与解决
  • 房屋安全鉴定报告有效期多久
  • Redux的使用
  • 深入理解 Redis:特性、应用场景与实践指南
  • Linux应用(3)——进程控制
  • (Arxiv-2025)MOSAIC:通过对应感知的对齐与解缠实现多主体个性化生成
  • 制造业多数据库整合实战:用 QuickAPI 快速生成 RESTful API 接入 BI 平台
  • outOfMemory内存溢出
  • Pandas数据结构(DataFrame,字典赋值)
  • 谈谈对this的理解
  • CVE-2025-2502 / CNVD-2025-16450 联想电脑管家权限提升漏洞
  • 用 Trae 玩转 Bright Data MCP 集成
  • CiaoTool 批量钱包 多对多转账实战:跨链应用全解析
  • Decision Tree Model|决策树模型
  • 由浅及深:扫描电子显微镜(Scanning Electron Microscope,SEM)
  • CTFHub靶场之SSRF Gopher POST请求(python脚本法)
  • OpenWrt | 在 PPP 拨号模式下启用 IPv6 功能
  • 代码随想录算法训练营第六天 - 哈希表2 || 454.四数相加II / 383.赎金信 / 15.三数之和 / 18.四数之和
  • Java 中 wait 与 notify 的详解:线程协作的关键机制
  • Linux下编译Gmsh
  • api-ms-win-crt-runtime-l1-0.dll 丢失或错误的详细解决方法,教你最靠谱的解决方法
  • 如何在QT的pro文件中判断当前使用arm架构还是x86