06二分查找理解与应用

1. 二分查找的思想

二分查找针对的是一个有序的数据集合,查找的思想类似于分治思想,每次都与区间中间的元素对比,将区间的大小缩小为原来的一半,直至找到所找的元素或者区间的大小变为0

  • 二分查找的时间复杂度:o(logn),非常高效
  • 二分查找应用的场景比较局限:(1)在顺序表(数组)上进行使用(需要数据结构支持随机访问的特点才能体现其查找优势)(2)针对插入和删除特别频繁的动态数组不太适合使用二分查找,因为维护序列的有序性所需要的成本太高(3)数据量太小(无法体现其优势)和数据量太大(使用的是数组,需要连续的内存)都不适合使用二分查找。

2.二分查找的基本实现

其基本实现注意三个点:

  • 终止条件是low=high,因为此时区间大小为0
  • mid的计算方式最好为low + ((high-low)>>1)(注意多个括号,运算符的优先级)
  • low和high的值的更新问题,避免陷入死循环

非递归实现:

class Solution{

    public int bSearch(int[] a, int n, int value){
        if(n<=0) return;
        int low = 0, high = n-1;
        while(low<=high){
            int mid = low + ((high-low)>>1);   //防止low+high的值过大溢出,另外位运算比除法运算要快
            if(a[mid] == value){           
                return mid;
            }else if(a[mid]>value){
                high = mid - 1;
            }else{
                low = mid + 1;
            }
        }
        return -1;
    };
}

递归实现:

class Solution{
    public int bSearch(int a[], int n, int value){
        if(n<=0) return;
        return bSearchRecur(a, 0, n-1, value);
    };

    public int bSearchRecur(int[]a, int low, int high, int value){
        if(low > high) return -1;
        int mid = low + ((high - low) >> 1);
        if(a[mid]==value)
            return mid;
        if(a[mid] > value){                                //value位于a[mid]左侧
            return bSearchRecur(a, low, mid-1, value);
        }else{                                            //value位于a[mid]右侧
            return bSearchRecur(a, mid+1, high, value);  
        }
    };
}

3. 二分查找的变形问题

(1) 查找第一个值等于给定值的元素

class Solution{
    public int bSearchFisrt(int[]a, int n, int value){
        if(n<=0) return;
        return bSFirst(a, 0, n-1, value);
    };
    
    pulic int bSFisrt(int[]a, int low, int high, int value){
        if(low > high) return -1;
        int mid = low + ((high-low)>>1);
        if(a[mid] > value) 
            return bFsearch(a, low, mid-1, value);
        else if(a[mid]<value)
             return bFsearch(a, mid+1, high, value);
        else{
            if(mid==0 || a[mid-1]!=value)          //关键步骤,要么mid已经是数组的第一个元素,要么比mid小的下标对应的数组值不等于value,否则继续向前找
                return mid;
            return bFsearch(a, low, mid-1, value);
        }
       
    };
}

(2) 查找最后一个值等于给定值的元素

class Solution{
    public int bSearchFisrt(int[]a, int n, int value){
        if(n<=0) return;
        return bSFirst(a, 0, n-1, value);
    };
    
    pulic int bSFisrt(int[]a, int low, int high, int value){
        if(low > high) return -1;
        int mid = low + ((high-low)>>1);
        if(a[mid] > value) 
            return bFsearch(a, low, mid-1, value);
        else if(a[mid]<value)
             return bFsearch(a, mid+1, high, value);
        else{
            if(mid==a.lengh-1 || a[mid+1]!=value)          //关键步骤,要么mid已经是数组的最后个元素,要么比mid大的下标对应的数组值不等于value,否则继续向后找
                return mid;
            return bFsearch(a, mid+1, high, value);
        }
       
    };
}

(3)查找第一个大于等于给定值的元素

class Solution{
    public int bSearchFisrt(int[]a, int n, int value){
        if(n<=0) return;
        return bSFirst(a, 0, n-1, value);
    };
    
    pulic int bSFisrt(int[]a, int low, int high, int value){
        if(low > high) return -1;
        int mid = low + ((high-low)>>1);
        if(a[mid] >= value){ 
            if(mid == 0|| a[mid-1] < value)   //关键步骤,当mid下标对应元素为数组第一个元素或者mid-1下标对应元素的值比value值要小时返回,否则继续向前找
                return mid;
            return bFsearch(a, low, mid-1, value);
        }
        else
             return bFsearch(a, mid+1, high, value);       
    };
}

(4)查找最后一个小于等于给定值的元素

class Solution{
    public int bSearchFisrt(int[]a, int n, int value){
        if(n<=0) return;
        return bSFirst(a, 0, n-1, value);
    };
    
    pulic int bSFisrt(int[]a, int low, int high, int value){
        if(low > high) return -1;
        int mid = low + ((high-low)>>1);
        if(a[mid] > value) 
            return bFsearch(a, low, mid-1, value);
        else{
             if(mid == a.length-1 || a[mid+1] > value)   //关键步骤,当mid下标对应元素为数组最后一个元素或者mid+1下标对应元素的值比value值要大时返回,否则继续向后找
                 return mid;

             return bFsearch(a, mid+1, high, value);
        }       
    };
}

4. 二分查找问题实战

(1)求一个数的平方根?要求误差小于某个具体值range

class Solution{
    
    public double getSqrt(int num, double range){
        if(num<0) return;
        return mySqrt(num, 0.0, (double)num, range);
    };

    
    public double mySqrt(int num, double low, double high, double range){
        double mid = (low + high)/2f;
        if(num < mid*mid){                               //当num < mid*mid时,mid的值偏大,故需要在小一点值的区间查找
            return mySqrt(num, low, mid, range);  
        }else{                                        
            if(num - mid*mid > range){                //当num > mid*mid,且num-mid*mid>range的值时,mid的值偏小,需要在大一点值的区间查找
                return mySqrt(num, mid, high, range);
            }
            return mid;
        }
    };
}

(2)搜索旋转排序数组(leetcode33)

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

思路:观察循环有序数组我们可以发现若将其二分,二分后的两个数组,要么左侧数组为有序数组、右侧数组为循环数组;要么左侧数组为循环数组、右侧数组为有序数组,按照这个特性,可以完成查找工作:

  • 如果首元素nums[l]小于 nums[mid],说明前半部分是有序的,后半部分是循环有序数组;
  •  如果首元素nums[l]大于 nums[mid],说明后半部分是有序的,前半部分是循环有序的数组;
  •  如果目标元素在有序数组范围中,使用二分查找;
  •  如果目标元素在循环有序数组中,设定数组边界后,使用以上方法继续查找。
class Solution {
    public int search(int[] nums, int target) {
        int len = nums.length;
        if(len<=0) 
            return -1;
        if(len == 1){
            return nums[0] == target ? 0:-1;
        }
        int low = 0, high = len-1;
        while(low <= high){
            int mid = low + ((high - low)>>1);
            if(nums[mid] == target) return mid;
            if(nums[low]<=nums[mid]){     //表示左边有序
                if(nums[low]<=target && target < nums[mid])    //target位于左侧有序数组,二分查找target
                    high = mid-1;
                else                                     //target位于右侧循环数组,继续分组
                    low = mid+1;
            }else{
                if(nums[mid]<target && target<=nums[high])
                    low = mid + 1;                     //target位于右侧有序数组,二分查找target
                else
                    high = mid-1;                      //target位于左侧循环数组,继续分组
            }
        }
        return -1;
    }
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值