二分查找
详解
一、什么是二分查找
二分查找(Binary Search)是一种高效的查找算法,适用于已排序的数组。其核心思想是:每次通过比较数组中间元素与目标值,将搜索范围缩小一半,不断调整左右边界,直至找到目标元素或确定目标不存在。
核心要点:已排序的数组,通过中间索引比较,不满足条件则缩小边界
二、为什么需要二分查找
在数据量庞大的情况下,线性查找(逐个检查)效率极低。例如,在包含 100 万个元素的数组中查找一个值:
- 线性查找:平均需要 50 万次比较
计算前提:
- 数组包含100万个元素(n = 1,000,000)
- 假设目标元素一定存在于数组中
- 目标元素等概率出现在任意位置
计算过程:
- 如果目标在第1个位置:需要1次比较
- 如果目标在第2个位置:需要2次比较
- ...
- 如果目标在第1,000,000个位置:需要1,000,000次比较
平均比较次数 = (1 + 2 + 3 + ... + n) ÷ n
= [n × (n+1) ÷ 2] ÷ n
= (n+1) ÷ 2
= (1,000,000 + 1) ÷ 2
= 500,000.5 ≈ 50万次比较- 二分查找:最多只需 20 次比较
计算前提:
- 数组包含100万个元素且已排序
计算过程:
- 设k为需要的比较次数
- 每次比较后,搜索范围变为原来的一半
- 经过k次比较后,搜索范围大小为:1,000,000 ÷ 2^k
- 当搜索范围≤1时,查找结束:1,000,000 ÷ 2^k ≤ 1
- 即:2^k ≥ 1,000,000
求解k:
k = log₂(1,000,000) ≈ 19.93
由于比较次数必须是整数,向上取整得到 20次比较。以上可以看出两者平均比较次数相差 2.5w 倍。
二分查找显著提升了大规模数据查找的效率,是算法优化的重要手段。
当数据已经排序时,不利用这一特性而使用线性查找是巨大的资源浪费。
三、适用范围
- 数据必须已排序:这是二分查找的前提条件
- 静态或较少变动的数据集:频繁插入/删除会使维护排序的开销超过查找收益
- 内存中连续存储的数据结构:如数组,不适用于链表等非连续存储结构
- 查找频率远高于插入/删除的场景:如字典查询、数据库索引
详细计算见:二分查找-适用范围计算验证
四、能解决的问题
- 基础查找:判断元素是否存在及获取其位置
- 边界问题:
- 查找第一个大于/等于目标值的元素
- 查找最后一个小于/等于目标值的元素
- 近似查找:找出最接近目标值的元素
- 算法基础:作为其他复杂算法的组成部分
五、复杂度与性能分析
- 时间复杂度:O(log n)
- 每次比较都将搜索范围缩小一半
- 最坏情况下只需 log₂n 次比较即可完成查找
- 性能稳定,不受数据分布影响
- 空间复杂度:
- 迭代实现:O(1),仅需常数级额外空间
- 递归实现:O(log n),调用自身,需考虑调用栈空间
- 性能特点:
- 不随数据分布变化而波动,性能稳定
六、具体示例
注意事项:
- 边界条件问题:处理 left、right、mid 的更新时容易出错,特别是边界相等的情况
- 整数溢出:计算 mid 时应使用
left + (right-left)//2而非(left+right)//2 - 死循环问题:循环条件应为
left <= right,更新边界时要确保范围确实缩小
1. 基础二分查找实现(查找目标值)
public static int binarySearch(int[] arr, int target) {
int left = 0, right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 避免整数溢出
if (arr[mid] == target) {
return mid; // 找到目标,返回索引
} else if (arr[mid] < target) {
left = mid + 1; // 目标在右半部分
} else {
right = mid - 1; // 目标在左半部分
}
}
return -1; // 未找到目标
}2. 边界查找示例(查找第一个大于等于目标值的元素)
public static int lowerBound(int[] arr, int target) {
int left = 0, right = arr.length - 1;
int result = arr.length; // 初始化为数组长度,表示所有元素都小于目标值
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] >= target) {
result = mid;
right = mid - 1; // 继续在左半部分查找
} else {
left = mid + 1;
}
}
return result;
}3. 实际应用场景
假设在一个包含 10000 个已排序学生成绩的数组中查找是否有人得了 85 分:
public class StudentScoresSearch {
public static void main(String[] args) {
// 假设有一个已排序的10000个学生成绩数组
int[] scores = new int[10000];
// 实际应用中这里应该加载真实数据
int targetScore = 85;
int resultIndex = binarySearch(scores, targetScore);
if (resultIndex != -1) {
System.out.println("找到了得分为85分的学生,索引位置: " + resultIndex);
} else {
System.out.println("没有学生得到85分");
}
// 说明:在10000个元素的数组中,二分查找最多只需14次比较(log₂10000≈14)
}
// 这里插入上面定义的binarySearch方法
}七、与其他查找方法的横向对比
| 查找方法 | 时间复杂度 | 空间复杂度 | 数据要求 | 优势 | 劣势 |
|---|---|---|---|---|---|
| 二分查找 | O(log n) | O(1) | 必须已排序 | 效率高,性能稳定 | 要求数据预先排序 |
| 线性查找 | O(n) | O(1) | 无需排序 | 实现简单,适用于任何数据 | 大数据量下效率低 |
| 哈希表查找 | 平均 O(1),最坏 O(n) | O(n) | 无需排序 | 平均情况下最快 | 需额外空间,最坏情况性能差,不支持范围查询 |
| 二叉搜索树 | 平均 O(log n),最坏 O(n) | O(n) | 动态维护有序 | 支持动态插入删除 | 不平衡时性能下降,需额外空间 |
| 插值查找 | 平均 O(log log n),最坏 O(n) | O(1) | 有序且均匀分布 | 数据均匀时更快 | 数据分布不均时性能差 |
八、总结
- 核心思想:分而治之,每次将搜索范围缩小一半,通过不断调整边界逼近目标
- 前提条件:数据必须已经排序,这是使用二分查找的必要条件
- 性能优势:时间复杂度 O(log n),在大数据量场景下优势显著
- 应用扩展:不仅用于精确查找,还可解决各类边界问题和最优化问题
- 实现关键:注意边界条件和整数溢出问题,确保算法正确性和鲁棒性
- 算法选择:应根据数据特性和操作需求选择合适的查找算法
- 静态有序数据:二分查找
- 动态数据:哈希表或平衡二叉搜索树
- 小规模数据:线性查找可能更简单实用
关联网络
演化日志
- v0.1 (2024-08-20):初始版本
- v0.2 (2026-01-10):修改详情,补充关联网络、演化日志
复习回顾
📈 轮次: 1 🕒 lastReview: 2026-01-10 12:10:40 📅 nextReview: 2026-01-17 00:00:00