算法——二分查找

二分查找(Binary Search)是一种高效的查找算法,适用于有序数组。通过每次将查找范围缩小一半,能够快速找到目标值或确定目标值不存在。相比线性查找(O(n)),二分查找的时间复杂度为 O(log n),效率更高。

一、二分查找的原理

二分查找又叫折半查找 ,基本原理是"分而治之 "的思想:每次将查找范围减半,从而大大减少比较的次数。它要求数据必须有序,因为算法利用了数据的排序信息来决定目标值可能出现在哪一半。

详细的工作流程:
  1. 初始化边界 :我们首先设定查找的范围,low 指向数组的起始位置,high 指向数组的末尾位置。
  2. 计算中间点 :通过 mid = (low + high) / 2 计算当前查找范围的中间点 mid
  3. 比较中间值与目标值:
    • 如果 arr[mid] == target,那么就找到了目标值,直接返回 mid
    • 如果 arr[mid] > target,目标值一定在左半部分,将 high 更新为 mid - 1
    • 如果 arr[mid] < target,目标值一定在右半部分,将 low 更新为 mid + 1
  4. 重复步骤 2 和 3 :在每一步中,查找范围缩小一半,直到 low 超过 high,这时可以确定数组中没有目标值。
  5. 终止条件 :当 low > high 时,查找范围已经为空,意味着目标值不存在。

这个过程可以用递归或循环实现。在每次查找中,二分查找算法都会排除一半的元素,因此查找效率非常高。

二、二分查找的时间复杂度

1. 时间复杂度 O(log n)
  • 在每一次迭代或递归中,查找的范围都会缩小一半,因此执行的步骤数是 log n(以 2 为底的对数),其中 n 是数组的长度。
  • 假设数组的长度为 16,第一次比较后,范围从 16 减少到 8,接着是 4,再到 2,最后是 1,因此总共需要 4 次比较,正好是 log2(16)
2. 空间复杂度 O(1)(循环实现)或 O(log n)(递归实现)
  • 循环版本只使用了几个额外的变量(lowhighmid),因此空间复杂度是常数 O(1)。
  • 递归版本则每次递归调用都会增加栈空间,递归的深度是 O(log n),所以空间复杂度是 O(log n)。

三、二分查找的伪代码

二分查找可以通过递归或循环两种方式实现,下面是伪代码版本的二分查找。

循环实现的伪代码:
java 复制代码
public static int binarySearch(int[] arr, int target) {

        int low = 0;  // 起始指针
        int high = arr.length - 1;  // 结束指针

        // 当 low <= high 时,继续查找
        while (low <= high) {
            // 计算中间位置,防止溢出
            int mid = low + (high - low) / 2;

            // 找到目标值
            if (arr[mid] == target) {
                return mid;
            }
            // 如果目标值在右半部分
            else if (arr[mid] < target) {
                low = mid + 1;
            }
            // 如果目标值在左半部分
            else {
                high = mid - 1;
            }
        }
        // 目标值不在数组中,返回 -1
        return -1;
    }
递归实现的伪代码:
java 复制代码
/**
     * 使用二分查找算法递归地在一个排序的整数数组中查找目标值。
     *
     * @param arr 排序的整数数组
     * @param low 搜索范围的最低索引
     * @param high 搜索范围的最高索引
     * @param target 目标值,要查找的整数
     * @return 找到的目标值的索引,如果目标值不存在则返回-1
     */
    public static int binarySearchRecursive(int[] arr, int low, int high, int target) {
        // 当low大于high时,表示搜索范围为空,返回-1表示目标值不存在
        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, mid + 1, high, target);  // 右半部分继续查找
        }
        // 如果中间位置的元素大于目标值,在左半部分继续查找
        else {
            return binarySearchRecursive(arr, low, mid - 1, target);  // 左半部分继续查找
        }
    }

四、二分查找的代码实现

1. 循环版本:
java 复制代码
public class BinarySearch {
    public static int binarySearch(int[] arr, int target) {
        int low = 0;
        int high = arr.length - 1;

        while (low <= high) {
            int mid = low + (high - low) / 2;  // 防止溢出

            if (arr[mid] == target) {
                return mid;  // 找到目标,返回索引
            } else if (arr[mid] < target) {
                low = mid + 1;  // 在右半部分继续查找
            } else {
                high = mid - 1;  // 在左半部分继续查找
            }
        }

        return -1;  // 目标值不存在
    }

    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9, 11, 13, 15};
        int target = 7;
        int result = binarySearch(arr, target);
        if (result != -1) {
            System.out.println("目标值 " + target + " 的索引为: " + result);
        } else {
            System.out.println("目标值 " + target + " 不存在");
        }
    }
}
2. 递归版本:
java 复制代码
public class BinarySearchRecursive {
    public static int binarySearchRecursive(int[] arr, int low, int high, int target) {
        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, mid + 1, high, target);  // 右半部分继续查找
        } else {
            return binarySearchRecursive(arr, low, mid - 1, target);  // 左半部分继续查找
        }
    }

    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9, 11, 13, 15};
        int target = 7;
        int result = binarySearchRecursive(arr, 0, arr.length - 1, target);
        if (result != -1) {
            System.out.println("目标值 " + target + " 的索引为: " + result);
        } else {
            System.out.println("目标值 " + target + " 不存在");
        }
    }
}

五、二分查找的注意事项

  1. 数组必须是有序的:二分查找只适用于有序数组。如果数组未排序,则必须先排序才能使用二分查找。
  2. 边界条件 :需要仔细处理 lowhigh 的更新。特别是在循环和递归时,确保范围的更新不出错。
  3. 整数溢出问题 :在计算 mid 时,直接使用 (low + high) / 2 可能会导致整数溢出,因此推荐使用 low + (high - low) / 2 这种安全的写法。

六、二分查找的变种

  1. 查找第一个等于目标值的元素:在有重复元素的情况下,查找第一个等于目标值的元素。
  2. 查找最后一个等于目标值的元素:在有重复元素的情况下,查找最后一个等于目标值的元素。
  3. 查找第一个大于等于目标值的元素:在有序数组中查找第一个大于或等于目标值的元素。
  4. 查找最后一个小于等于目标值的元素:在有序数组中查找最后一个小于或等于目标值的元素。

七、应用场景

  1. 有序数据的快速查找:二分查找广泛用于查找有序数据中的目标值,比如数据库索引查找。
  2. 算法题目:二分查找在很多算法题中出现,特别是涉及到查找问题的优化时,比如查找旋转数组中的最小值、找峰值等。
  3. 查找临界值:二分查找也可以用于优化问题,查找某种操作的最优解,比如在一些最小化或最大化问题中通过二分查找进行边界值查找。

二分查找是一种非常高效的查找算法,在有序数据中有广泛应用,其思想简单但实用。

相关推荐
HelloDam1 分钟前
基于元素小组的归并排序算法
后端·算法·排序算法
HelloDam1 分钟前
基于连贯性算法的多边形扫描线生成(适用于凸多边形和凹多边形)【原理+java实现】
算法
chushiyunen5 分钟前
dom操作笔记、xml和document等
xml·java·笔记
whisperrr.6 分钟前
【spring01】Spring 管理 Bean-IOC,基于 XML 配置 bean
xml·java·spring
chushiyunen8 分钟前
tomcat使用笔记、启动失败但是未打印日志
java·笔记·tomcat
天上掉下来个程小白15 分钟前
HttpClient-03.入门案例-发送POST方式请求
java·spring·httpclient·苍穹外卖
ModestCoder_24 分钟前
将一个新的机器人模型导入最新版isaacLab进行训练(以unitree H1_2为例)
android·java·机器人
逸狼26 分钟前
【Java 优选算法】二分算法(下)
数据结构
a180079310801 小时前
软件工程面试题(二十二)
java·面试·软件工程
RainbowSea1 小时前
4. RabbitMQ 发布确认的配置详细说明
java·消息队列·rabbitmq