JAVA中的二分查找详解

二分法查找是一种分治策略算法,也叫折半搜索,他将问题分解为规模更小的子问题,分而治之,逐一解决。采用二分法查找的前提条件问:采用顺序存储结构,数据元素排序。

二分法查找算法设计

  1. 初始化指针:设置两个指针,一个指针数组的起始位置(通常为left),另一个指向数组的结束位置(称为right)。
  2. 循环条件:只要left小于等于right,就继续循环
  3. 计算中间索引:计算中间索引mid。为了防止整数溢出,通常使用mid=left+(right-left)/2来计算。
  4. 比较中间元素:将数组中的中间元素arr[mid]与目标值target进行比较。
  5. 匹配条件

如果arr[mid]等于target,则找到目标值,返回mid作为目标值的索引。

如果arr[mid]小于target,则将left更新为mid+1,表示目标值在数组的右半边。

如果arr[mid]大于target,则将right更新为mid-1,表示目标值在数组的左半边。

      6.循环结束:如果循环结束时没有找到目标值,返回-1表示目标值不在数组中。

 下面以arr[1,2,3,4,5,6,7]举例子:

1.当数组长度为奇数

middle=(0+6)/2=3

mid
arr[]1234567
下标0123456

2.当数组长度为偶数

middle=(0+7)=3.5,下向取整为3

mid
arr[]12345678
下标01234567

当取中间元素,遇到两边数据个数不同时,并不影响我们查找元素,只需要规定是向上或向下取整。

所以数组长度是偶数还是奇数这个并不重要,也不影响怎么排除的问题,无非是多排除一个数字或者少排除一个数字。

3.实现过程 

在 {1,2,3,4,5,6,7,8,9,10} 中查找元素9。

第一步要找到中间元素,设置两个变量left、high,分别指向数组第一个元素下标和最后一个元素下标,从而控制数组的范围,再根据low和high确定中间元素的下标mid

leftmidright
arr[]12345678910
下标0123456789

据mid锁定的元素,和查找的元素(9)比较,确定新的查找范围、left和right

比较5和9的大小,目标元素target>middle,那么left移动到middle+1=5的位置

然后重置middle

leftmidright
arr[]12345678910
下标0123456789

 比较8和9的大小,目标元素target>middle,那么left移动到middle+1=8的位置

left

mid

right
arr[]12345678910
下标0123456789

 此时,mid=8,arr[mid]=9,与要查找的元素相同,即已经找到了,并返回其下标

下面是代码实现: 

  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;
  }
1.为什么while循环的条件中是<=,而不是<?

因为初始化的right的赋值是nums.length-1,即最后一个元素的索引,而不是nums.length。

这两者可能出现在不同功能的二分查找中,区别是:前者相等于两端都闭区间[left,right],后者相当于左闭右开区间[left,right),因为索引大小nums.length是越界的。

2.此算法的缺陷是什么?

比如有序数组nums=[1,2,2,2,3],target=2,此算法返回到索引是2,是正确的,但是如果我们想返回第一个出现的2,和最后一个出现的2呢?那么这个算法是无法实现的。

所以我们后面引出来了另外两种算法

寻找左侧边界的二分搜索

代码实现:

      public static int left_bound(int[] nums,int target){
          if (nums.length==0){
              return -1;
          }
          int left =0;
          int right=nums.length;//注意这里
          
          while(left<right){
              int mid=(left+right)/2;
              if (nums[mid]==target){
                  right=mid;
              }else if (nums[mid]<target){
                  left=mid+1;
              }else if (nums[mid]>target){
                  right=mid;
              }
          }
          return left;
      }
1.为什么while(left<right)而不是<=?

因为初始化right=nums.length而不是right=nums.length-1,因此每次循环的搜索区间[left,right)左闭右开。

2.为什么没有返回-1的操作,如果nums中不存在target这个值怎么办?

对于这个数组,算法会返回1,这个1的含义可以这样子解读:nums中小于2的元素有1个。

比如对于有序数组nums=[2,3,5,7],target=1,算法会返回0,含义是nums小于1的元素有0个。如果target=8,算法会返回4,含义是:nums中小于8的元素有4个。

3.为什么该算法能够搜索到左侧边界

关键在于这段代码:

if(nums[mid]==target)
right=mid;

可见,找到了target不要立即返回,而是缩小[搜索区域]的上界right,在区间[left,mid]中继续搜索,即不断向左收缩,达到锁定左侧边界的目的。

总结:

时间复杂度:
二分法的时间复杂度是 O(log n),其中 n 是数组的长度或搜索空间的大小。这是因为每次迭代都会将搜索区间减半,因此需要 log₂(n) 次迭代来将区间缩小到一个点。

空间复杂度:
二分法的空间复杂度通常是 O(1),因为它只需要常数级别的额外空间来存储索引或中间值。

注意事项:

  • - 二分法要求搜索区间是有序的,因此它不适用于无序数据的搜索。
  • - 对于非单调函数,二分法可能无法找到零点或最优解。
  • - 在实际应用中,二分法通常需要结合其他算法或逻辑来处理边界条件和特殊情况。 
  • 26
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值