二分查找及其Java代码实现

本文通过生动的例子解释了二分查找算法的工作原理,对比了简单查找的效率,阐述了二分查找适用条件,并提供了Java实现代码。

假设我们在词典中查找一个k开头的单词,我们会怎么做呢?

  1. 从词典第一页开始一页一页的翻页,然后直到翻到k开头的单词。
  2. 直接翻页到词典大概中间的位置,然后根据词典a-z排列规律,判断翻到的页在k之前,还是之后,然后继续翻页。

其实这就是一个查找问题,上面第二种方法就是 二分查找

我们再举一个例子:
我自己随便想一个 1-100 之间的数字,然后让你来猜,你每次猜测之后我都会告诉你,猜大了还是猜小了。(假设我心中想的数字是60)

  1. 第一种方式:假设你从 1 开始依次往上猜,要猜到 60 你得猜60次才能猜到。这种方式就是 简单查找。(当然聪明如你肯定不会以这种方式来猜)
  2. 第二种方式:从 50 开始,小了,排除1-50,排除了一半数字;接下来你猜 75,大了,又排除了一半数字75-100;接下来你猜 63,大了;依次类推,你每次猜测的都是中间的数字,从而每次都将余下的数字排除一半,无论我心中的所想的数字是几,你在7步之内就能猜到。(大家想想为什么是7次?给个提示,2的6次方是64,2的7次方是128。)

上述第二种方式就是 二分查找

一般而言,对于包含n个元素的列表,用二分查找最多需要 logn 步(log以2为底),用简单查找最多需要 n

接下来我们看看如何编写二分查找的Java代码,有两种方式,一种是利用循环,另一种是利用递归(递归是什么此篇文章就不展开讲了,之后再讲。)

另外需要注意的是,二分查找只能应用在排好顺序的数组或列表上。


/**
* BinSearch
*/
public class BinSearch {
    public static void main(String[] args) {
        int[] arr = {1,4,8,16,45,48,78};
        int index = recursionBinarySearch(arr,48,0,arr.length-1);
        int index2 = commonBinarySearch(arr,48);
        System.out.println(index);
        System.out.println(index2);
    }
    //递归
    public static int recursionBinarySearch(int[] arr,int key,int low,int high){
        
        if(key < arr[low] || key > arr[high] || low > high){
            return -1;              
        }
        
        int middle = (low + high) / 2;          //初始中间位置
        if(arr[middle] > key){
            //比关键字大则关键字在左区域
            return recursionBinarySearch(arr, key,low,middle-1);
        }else if(arr[middle] < key){
            //比关键字小则关键字在右区域
            return recursionBinarySearch(arr, key,middle+1,high);
        }else {
            return middle;
        }
    }
    //循环
    public static int commonBinarySearch(int[] arr,int key){
        int low = 0;
        int high = arr.length - 1;
        int middle = 0;         //定义middle
        
        if(key < arr[low] || key > arr[high] || low > high){
            return -1;              
        }
        
        while(low <= high){
            middle = (low + high) / 2;
            if(arr[middle] > key){
                //比关键字大则关键字在左区域
                high = middle - 1;
            }else if(arr[middle] < key){
                //比关键字小则关键字在右区域
                low = middle + 1;
            }else{
                return middle;
            }
        }
        return -1;      //最后仍然没有找到,则返回-1
    }
}


<think>好的,我现在需要回答用户关于二分查找思想及其Java代码实现的问题。用户之前已经问过Java中的>>运算符,现在转向另一个算法相关的问题,可能是正在学习基础算法Java编程。 首先,我需要回忆二分查找的基本概念。二分查找是一种高效的搜索算法,适用于已排序的数组或列表。它的核心思想是通过不断将搜索区间分成两半,缩小目标范围,直到找到目标元素或确定不存在。因为每次比较都能减少一半的搜索空间,所以时间复杂度是O(log n),这比线性查找的O(n)快很多。 接下来,用户可能想知道二分查找的具体步骤。我需要分步骤解释:首先确定左右边界,计算中间位置,比较中间元素与目标值,根据比较结果调整左右边界,重复直到找到或区间无效。 然后,考虑Java代码实现。需要写一个函数,接受排序好的数组和目标值,返回目标值的索引或-1。需要注意循环条件(left <= right),中间值的计算方式(防止溢出),以及边界调整(left = mid + 1 或 right = mid -1)。 同时,用户可能对边界条件和常见错误感兴趣,比如如何处理重复元素,或者中间值计算时(left + right)可能导致整数溢出的问题。这时候应该提到使用left + (right - left)/2来避免溢出。 另外,可能需要提到二分查找的变种,比如查找第一个或最后一个出现的位置,但用户的问题可能只需要基础的实现,所以暂时先不深入变种,除非用户后续提问。 还要考虑示例代码的正确性。例如,给出一个有序数组,比如[1,3,5,7,9],查找5,应该返回索引2。同时测试边缘情况,比如查找数组第一个元素或最后一个元素,或者查找不存在的元素。 在注意事项部分,需要强调数组必须是有序的,否则二分查找无法正常工作。另外,虽然时间复杂度低,但排序数组本身如果需要频繁插入或删除,可能不适合使用二分查找,因为维护有序性成本高。 最后总结二分查找的优缺点,适用场景,以及正确实现的要点。确保用户理解何时使用二分查找,以及如何正确编写代码避免常见错误。</think>以下是关于二分查找的详细说明和Java实现: --- ### 一、二分查找核心思想 二分查找(Binary Search)是一种针对**有序集合**的高效搜索算法,时间复杂度为 $O(\log n)$。其核心步骤为: 1. **区间划分**:确定搜索范围的左右边界(初始为整个数组) 2. **取中间值**:计算中间位置,比较中间元素与目标值 3. **缩小区间**: - 若中间值**等于目标** → 找到结果 - 若中间值**小于目标** → 目标在右半区间 - 若中间值**大于目标** → 目标在左半区间 4. **循环执行**:重复上述步骤,直到找到目标或区间无效 --- ### 二、Java代码实现(基础版) ```java public class BinarySearch { // 返回目标值的索引,不存在则返回-1 public static int binarySearch(int[] nums, int target) { int left = 0; int right = nums.length - 1; while (left <= right) { // 注意循环条件 int mid = left + (right - left) / 2; // 防止整数溢出 if (nums[mid] == target) { return mid; } else if (nums[mid] < target) { left = mid + 1; // 目标在右半区 } else { right = mid - 1; // 目标在左半区 } } return -1; // 未找到 } public static void main(String[] args) { int[] arr = {1, 3, 5, 7, 9}; System.out.println(binarySearch(arr, 5)); // 输出2 System.out.println(binarySearch(arr, 4)); // 输出-1 } } ``` --- ### 三、关键细节说明 #### 1. 循环条件 `left <= right` - 当 `left > right` 时,说明搜索区间已无效 - **错误示例**:若使用 `left < right`,当数组只有一个元素时会直接跳过循环 #### 2. 中间值计算方式 - **错误写法**:`mid = (left + right) / 2`(当 `left + right` 超过 `Integer.MAX_VALUE` 时溢出) - **正确写法**:`mid = left + (right - left) / 2` #### 3. 边界调整 - `left = mid + 1` 和 `right = mid - 1` 确保每次循环区间必然缩小 - 若写成 `right = mid` 或 `left = mid` 可能导致死循环 --- ### 四、算法特性 | 特性 | 说明 | |---------------------|--------------------------------------| | 时间复杂度 | $O(\log n)$ | | 空间复杂度 | $O(1)$(迭代实现) | | 数据要求 | **必须有序**(升序或降序排列) | | 适用场景 | 静态数据集、频繁查找、大数据量 | --- ### 五、常见问题及解决方案 #### 1. 重复元素的处理 基础版只能返回任意一个匹配位置,若需要找**第一个/最后一个出现的位置**,需使用变种算法: ```java // 查找第一个等于target的位置 int leftBound(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] >= target) { right = mid - 1; } else { left = mid + 1; } } return (left < nums.length && nums[left] == target) ? left : -1; } ``` #### 2. 浮点数二分 处理精度问题时,改用固定循环次数或设定误差阈值: ```java double binarySearchDouble(double[] nums, double target) { double eps = 1e-6; double left = 0, right = nums.length - 1; while (right - left > eps) { double mid = (left + right) / 2; if (nums[mid] < target) { left = mid; } else { right = mid; } } return left; } ``` --- ### 六、总结 - **核心优势**:效率极高,适合大规模有序数据 - **注意事项**:必须保证输入数据有序,注意边界条件处理 - **扩展应用**:数学问题求根、旋转数组查找、范围查询等 通过合理运用二分思想,可以高效解决许多与有序数据相关的搜索问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值