算法——二分查找

二分查找(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)。

三、二分查找的伪代码

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

循环实现的伪代码:
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;
    }
递归实现的伪代码:
/**
     * 使用二分查找算法递归地在一个排序的整数数组中查找目标值。
     *
     * @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. 循环版本:
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. 递归版本:
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. 查找临界值:二分查找也可以用于优化问题,查找某种操作的最优解,比如在一些最小化或最大化问题中通过二分查找进行边界值查找。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值