算法通关村——透彻理解二分查找

常见的查找算法有顺序查找、二分查找、插值查找,斐波那契查找,树表查找、分块查找、哈希查找等等。其实二分查找、插值查找以及斐波那契查找都可以归为一类——插值查找。插值查找和斐波那契查找是在二分查找的基础上的优化查找算法。
这些算法中最重要的无疑是Hash查找和二分查找,所以本节我们先来讲讲二分查找及其变形问题。

1. 二分查找与分治

在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如二分搜索、排序算法(快速排序,归并排序)等等……
二分查找就是将中间结果与目标进行比较,一次去掉一般,因此二分查找可以说是最简单、最典型的分治了
在这里插入图片描述

二分查找,不管是循环还是递归方式,我觉得应该达到写到闭着眼睛,一分钟就能写出来的地步。

循环的方式

常见的使用循环的方式来实现二分查找如下:

 public static int binarySearch(int[] array, int low, int high, int target) {
        while (low <= high) {
            int mid = (low + high)/2;
            if (array[mid] == target) {
                return mid ;
            } else if (array[mid] > target) {
                // 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
                high = mid -1;
            } else {
                // 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
                low = mid + 1;
            }
        }
        return -1;
    }

在具体操作的时候可能有多种方式的,包括循环体中的 high = mid -1;和low = mid + 1也有多种方式的,这需要与if后面的条件配合,我们不要给自己添麻烦,在理解的基础上熟记这种方式就行了。
但是如果你在面试时代码这样写,大概率只能得70分。因为这里有个比较重要的细节要注意。在计算机中,除的效率非常低,一般使用位移来代替,也就是说:

将:int mid = (low + high) /2;
换成:int mid = (low + high)>>1;

如果改成这样的话,面试官可能会继续问,还有什么问题。问题就是当low和high很大时, low + high 可能会溢出。因此我们可以这么写:
int mid = low+(high - low)>>1;
只要 low 和 high 不溢出,mid就一定不会溢出。
你觉得可以达到90分了,但是实际上是0分。因为位移运算符 >> 的优先级是比加减要低的,所以上面的代码等价结构是这样的。
(low+(high - low))>>1
很明显这不是我们预期的。解决方法也很简单,加括号就行了。所以最终的代码就是:

 public static int binarySearch2(int[] array, int low, int high, int target) {
        //递归终止条件
      while (low <= high){
            //这里不使用 low + high 是为了防止溢出,使用右移提高效率
            int mid = low + ((high - low) >> 1);
            if (array[mid] == target){
                return mid;
            } else if (array[mid] > target){
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return -1;
    }

这样的话,面试官就提不出什么问题了,而且上面这个优先级问题很多人只是理解了,并没有上机测试,因此很多面试官也不会注意到这里会有死循环的情况。

递归的方式

递归的代码不做过多解释,代码如下:

 public static int binarySearch1(int[] array, int low, int high, int target) {
        //递归终止条件
        if(low <= high){
            int mid = low + ((high - low) >> 1);
            if(array[mid] == target){
                return mid ; // 返回目标值的位置,从1开始
            }else if(array[mid] > target){
                // 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
                return binarySearch(array, low, mid-1, target);
            }else{
                // 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
                return binarySearch(array, mid+1, high, target);
            }
        }
        return -1; //表示没有搜索到
    }

2. 元素中有重复的二分查找

循环的方法(最后是线性查找)

假如在上面的基础上,元素存在重复,如果重复则找左侧第一个,请问该怎么做呢?
这里的关键是找到目标结果之后不是返回而是继续向左侧移动。第一种,也是最简单的方式,找到相等位置向左使用线性查找,直到找到相应的位置

    public static int search(int[] nums, int target) {
        if (nums == null || nums.length == 0)
            return -1;
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else {
                //找到之后,往左边找
                while (mid != 0 && nums[mid] == target)
                    mid--;
                if (mid == 0 && nums[mid] == target) {
                    return mid;
                }
                return mid + 1;
            }
        }
        return -1;
    }

递归的方法

假如重复的数量特别大,此时是否可以对内层的while进一步来二分呢?当然可以,我们可以在到相等的时候继续递归,找到目标元素之后根据要求继续递归寻找。
代码如下:

 public static int search2(int[] nums, int target) {
        if (nums == null || nums.length == 0)
            return -1;
        return binSearch2(nums,target,0,nums.length - 1);
    }

    private static int binSearch2(int[] nums, int target, int left, int right) {
        if (left > right)
            return -1;
        if (nums[left] == target){
            return left;
        }
        int mid = left + ((right - left) >> 1);
        if (nums[mid] > target){
            return binSearch2(nums,target,left,mid - 1);
        }
        if (nums[mid] < target){
            return binSearch2(nums,target,mid + 1, right);
        }
        //即当nums[mid] == targat时,继续二分查找
        return binSearch2(nums,target,left,right);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值