二分查找

详解

一、什么是二分查找

二分查找(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 倍。
二分查找显著提升了大规模数据查找的效率,是算法优化的重要手段。
当数据已经排序时,不利用这一特性而使用线性查找是巨大的资源浪费。

三、适用范围

  1. 数据必须已排序:这是二分查找的前提条件
  2. 静态或较少变动的数据集:频繁插入/删除会使维护排序的开销超过查找收益
  3. 内存中连续存储的数据结构:如数组,不适用于链表等非连续存储结构
  4. 查找频率远高于插入/删除的场景:如字典查询、数据库索引

详细计算见:二分查找-适用范围计算验证

四、能解决的问题

  1. 基础查找:判断元素是否存在及获取其位置
  2. 边界问题:
    • 查找第一个大于/等于目标值的元素
    • 查找最后一个小于/等于目标值的元素
  3. 近似查找:找出最接近目标值的元素
  4. 算法基础:作为其他复杂算法的组成部分

五、复杂度与性能分析

  • 时间复杂度:O(log n)
    • 每次比较都将搜索范围缩小一半
    • 最坏情况下只需 log₂n 次比较即可完成查找
    • 性能稳定,不受数据分布影响
  • 空间复杂度:
    • 迭代实现:O(1),仅需常数级额外空间
    • 递归实现:O(log n),调用自身,需考虑调用栈空间
  • 性能特点:
    • 不随数据分布变化而波动,性能稳定

六、具体示例

注意事项:

  1. 边界条件问题:处理 left、right、mid 的更新时容易出错,特别是边界相等的情况
  2. 整数溢出:计算 mid 时应使用 left + (right-left)//2 而非 (left+right)//2
  3. 死循环问题:循环条件应为 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)有序且均匀分布数据均匀时更快数据分布不均时性能差

八、总结

  1. 核心思想:分而治之,每次将搜索范围缩小一半,通过不断调整边界逼近目标
  2. 前提条件:数据必须已经排序,这是使用二分查找的必要条件
  3. 性能优势:时间复杂度 O(log n),在大数据量场景下优势显著
  4. 应用扩展:不仅用于精确查找,还可解决各类边界问题和最优化问题
  5. 实现关键:注意边界条件和整数溢出问题,确保算法正确性和鲁棒性
  6. 算法选择:应根据数据特性和操作需求选择合适的查找算法
    • 静态有序数据:二分查找
    • 动态数据:哈希表或平衡二叉搜索树
    • 小规模数据:线性查找可能更简单实用

关联网络

演化日志

  • 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