自学内容网 自学内容网

Java 二分查找算法详解及通用实现模板案例示范

1. 引言

二分查找(Binary Search)是一种常见的搜索算法,专门用于在有序数组或列表中查找元素的位置。它通过每次将搜索空间缩小一半,从而极大地提高了查找效率。相比于线性查找算法,二分查找的时间复杂度为 O(log n),在处理大规模数据时非常高效。

2. 二分查找算法的基本原理

二分查找要求搜索目标的数据集合是有序的。通过将数组从中间位置一分为二,逐步缩小搜索范围,最终找到目标元素或确定目标元素不存在。算法的关键在于,每次比较时,将要查找的元素与中间元素进行比较,并据此决定继续向左半部分或右半部分搜索。

3. 二分查找适用问题类型

二分查找算法适用于以下类型问题:

  1. 在有序数组中查找元素的索引
  2. 寻找有序数组中满足条件的边界值,如第一个大于等于目标值的元素或最后一个小于等于目标值的元素。
  3. 确定某一条件下的最优解,例如在单调递增或递减函数中查找某一阈值。

4. 二分查找通用实现模板

4.1 标准二分查找模板

首先,我们来看最基础的二分查找算法实现。这个模板适用于在有序数组中查找某个目标值,并返回其索引。

public class BinarySearch {
    /**
     * 标准二分查找,查找目标值的索引
     * @param arr 已排序的数组
     * @param target 要查找的目标值
     * @return 目标值的索引,如果不存在则返回 -1
     */
    public static int binarySearch(int[] arr, int target) {
        int left = 0;
        int 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;  // 没有找到目标值
    }

    public static void main(String[] args) {
        int[] sortedArray = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19};
        int target = 7;
        int result = binarySearch(sortedArray, target);

        if (result != -1) {
            System.out.println("目标值 " + target + " 在索引 " + result);
        } else {
            System.out.println("目标值 " + target + " 不存在于数组中");
        }
    }
}

解释

  • leftright 分别指向数组的左右边界。
  • 每次通过 mid = left + (right - left) / 2 计算中间索引。
  • 如果中间值 arr[mid] 恰好等于目标值,则返回 mid 作为目标值的索引。
  • 如果中间值小于目标值,表示目标值在右侧,则更新左边界 left = mid + 1
  • 如果中间值大于目标值,表示目标值在左侧,则更新右边界 right = mid - 1
4.2 二分查找的变体模板

除了标准的二分查找,还有一些变体问题常常需要我们在查找过程中找到特定的边界值。

查找第一个大于等于目标值的元素
public static int findFirstGreaterOrEqual(int[] arr, int target) {
    int left = 0;
    int right = arr.length - 1;
    int result = -1;

    while (left <= right) {
        int mid = left + (right - left) / 2;

        if (arr[mid] >= target) {
            result = mid;  // 记录当前 mid 作为可能的结果
            right = mid - 1;  // 继续在左半部分查找
        } else {
            left = mid + 1;  // 在右半部分查找
        }
    }

    return result;
}
查找最后一个小于等于目标值的元素
public static int findLastLessOrEqual(int[] arr, int target) {
    int left = 0;
    int right = arr.length - 1;
    int result = -1;

    while (left <= right) {
        int mid = left + (right - left) / 2;

        if (arr[mid] <= target) {
            result = mid;  // 记录当前 mid 作为可能的结果
            left = mid + 1;  // 继续在右半部分查找
        } else {
            right = mid - 1;  // 在左半部分查找
        }
    }

    return result;
}

5. 二分查找的时序图

为了更好地理解二分查找的执行过程,我们可以通过时序图展示二分查找的各个步骤。时序图展示了从开始到查找到目标值(或确认目标值不存在)的过程。

时序图

在这里插入图片描述

6. 案例分析:电商系统中的二分查找

在电商系统中,二分查找可以被应用在很多场景中,例如:

  1. 商品价格查询:在大量已排序的商品价格列表中快速找到某一商品价格,或者确定某个商品价格在列表中的位置。
  2. 库存检查:通过二分查找,可以快速确定某一商品的库存量,或者查找某类商品的库存是否充足。
  3. 订单历史记录查询:在按时间排序的订单历史记录中,快速查找某一时间段的订单。
案例 1:价格查询

假设电商系统有一份已排序的商品价格列表,用户需要查询某个商品的价格是否存在,或者找到第一个高于某个价格的商品。

public class PriceQuery {
    public static int findFirstPriceGreaterOrEqual(int[] prices, int targetPrice) {
        int left = 0;
        int right = prices.length - 1;
        int result = -1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (prices[mid] >= targetPrice) {
                result = mid;
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }

        return result;
    }

    public static void main(String[] args) {
        int[] prices = {100, 200, 300, 400, 500, 600, 700};
        int targetPrice = 350;
        int index = findFirstPriceGreaterOrEqual(prices, targetPrice);

        if (index != -1) {
            System.out.println("第一个大于等于 " + targetPrice + " 的价格是: " + prices[index]);
        } else {
            System.out.println("没有找到大于等于 " + targetPrice + " 的商品价格");
        }
    }
}
案例 2:订单历史记录查询

在按日期排序的订单记录中,用户希望快速找到某一天的订单记录,或者查找最近的一笔订单。

public class OrderQuery {
    public static int findClosestOrder(int[] orders, int targetDate) {
        int left = 0;
        int right = orders.length - 1;
        int closest = -1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (orders[mid] == targetDate) {
                return mid;  // 找到精确匹配的订单
            } else if (orders[mid] < targetDate) {
                closest = mid;  // 记录当前最近的订单
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

        return closest;  // 返回最近的订单
    }

    public static void main(String[] args) {
        int[] orderDates = {20220101, 20220201, 20220301, 20220401, 20220501};
        int targetDate = 20220315;
        int index = findClosestOrder(orderDates, targetDate);

        if (index != -1) {
            System.out.println("最近的订单日期是: " + orderDates[index]);
        } else {
            System.out.println("没有找到合适的订单");
        }
    }
}

7. 二分查找的常见问题

虽然二分查找看似简单,但在实现过程中容易遇到一些问题。

  1. 越界问题:由于计算中间索引时使用 (left + right) / 2,当 leftright 很大时,可能会导致整型溢出。为此,推荐使用 left + (right - left) / 2 计算中间索引。
  2. 终止条件不清晰:二分查找的循环条件应为 left <= right,而不是 left < right,否则可能会漏掉最后一个可能的索引位置。

原文地址:https://blog.csdn.net/weixin_39996520/article/details/142966317

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!