1. 概述

本文将介绍二分查找相较于线性查找的核心优势,并展示其在Java中的具体实现方式。

2. 高效查找的必要性

假设我们经营一个葡萄酒电商平台,每天有数百万用户访问。用户通过应用筛选价格低于n美元的商品,从结果中选择并添加购物车。每秒都有海量查询需要快速响应。

后端算法最初使用线性查找,逐个比较用户价格限制与每瓶酒的价格,返回符合条件的商品。这种做法的时间复杂度为*O(n)*,意味着:

  • 商品数量增长时,查询时间线性增加
  • 新增商品会直接拖慢搜索速度

如果改用排序存储 + 二分查找,复杂度可优化至*O(log n)*:

  • 数据量增长时,查询时间缓慢增加
  • 性能提升非成比例,尤其适合大数据量场景

3. 二分查找

核心思路:将目标值(key)与数组中间元素比较,根据结果排除一半数据,在剩余部分继续查找。关键前提:数组必须已排序。若最终剩余部分为空,说明目标不存在。

3.1. 迭代实现

public int runBinarySearchIteratively(
  int[] sortedArray, int key, int low, int high) {
    int index = Integer.MAX_VALUE;
    
    while (low <= high) {
        int mid = low  + ((high - low) / 2);
        if (sortedArray[mid] < key) {
            low = mid + 1;
        } else if (sortedArray[mid] > key) {
            high = mid - 1;
        } else if (sortedArray[mid] == key) {
            index = mid;
            break;
        }
    }
    return index;
}

⚠️ 注意中间索引计算方式int mid = low + ((high - low) / 2)
若直接使用 (low + high) / 2,当数组元素超过 2³⁰ 时可能溢出(sum 超过 int 最大值)。

3.2. 递归实现

public int runBinarySearchRecursively(
  int[] sortedArray, int key, int low, int high) {
    int middle = low  + ((high - low) / 2);
        
    if (high < low) {
        return -1;
    }

    if (key == sortedArray[middle]) {
        return middle;
    } else if (key < sortedArray[middle]) {
        return runBinarySearchRecursively(
          sortedArray, key, low, middle - 1);
    } else {
        return runBinarySearchRecursively(
          sortedArray, key, middle + 1, high);
    }
}

3.3. 使用 Arrays.binarySearch()

int index = Arrays.binarySearch(sortedArray, key);

直接调用 Java 工具类方法,传入已排序数组和目标值。

3.4. 使用 Collections.binarySearch()

int index = Collections.binarySearch(sortedList, key);

适用于 List 集合(需已排序),例如 ArrayList<Integer>

3.5. 处理重复元素的排序数组

当数组含重复元素时,标准二分查找会返回任意匹配位置。若需获取所有匹配项(窗口范围),需修改逻辑:

查找首次出现位置

int startIndexSearch(int[] sortedArray, int target) {
    int left = 0;
    int right = sortedArray.length - 1;
    int result = -1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (sortedArray[mid] == target) {
            result = mid;
            right = mid - 1; // 继续向左搜索
        } else if (sortedArray[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return result;
}

查找末次出现位置

int endIndexSearch(int[] sortedArray, int target) {
    int left = 0;
    int right = sortedArray.length - 1;
    int result = -1;

    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (sortedArray[mid] == target) {
            result = mid;
            left = mid + 1; // 继续向右搜索
        } else if (sortedArray[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return result;
}

获取所有匹配索引

List<Integer> runBinarySearchOnSortedArraysWithDuplicates(int[] sortedArray, Integer key) {
    int startIndex = startIndexSearch(sortedArray, key);
    int endIndex = endIndexSearch(sortedArray, key);
    return IntStream.rangeClosed(startIndex, endIndex)
      .boxed()
      .collect(Collectors.toList());
}

3.6. 性能分析

选择递归还是迭代实现主要看个人偏好,但需注意:

  1. 迭代优势:无栈开销,内存占用低,无栈溢出风险
  2. 递归劣势
    • 维护调用栈增加开销
    • 大数据集可能触发 StackOverflowException
  3. 递归优势:代码更简洁易读

适用场景对比

  • 大数据量:二分查找显著优于线性查找
  • 小数据量:线性查找可能更快(理论分析,需实测验证)

重要考量:二分查找要求数据已排序,排序本身有成本(如归并排序复杂度 *O(n log n)*)。需根据实际需求权衡。

4. 结论

本文介绍了二分查找算法的Java实现及适用场景。相比线性查找,它在处理大规模排序数据时能显著提升性能。实际应用中需结合数据规模、排序成本等因素综合选择。

完整代码示例见 GitHub仓库


原始标题:Binary Search Algorithm in Java | Baeldung

« 上一篇: EGit 完全指南
» 下一篇: Java Weekly, 第195期