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

二分查找与分治

1、分治的概念

​ 在计算机科学中,分治法是一种很重要的算法。字面解释是“分而治之”,就是把一个复杂的问题分解成两个或者更多的相同或相似的子问题,再把子问题分成更小的子问题······直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如二分搜索、排序算法(快速排序,归并排序)等等······

2、二分查找

​ 任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易求解,解题所需的计算时间也越少。要想直接解决一个规模较大的问题,有时是相当困难的。

​ 二分查找就是将中间结果与目标进行比较,一次去掉一半,因此二分查找可以说是最简单、最典型的分治算法。

分治和递归是一回事吗?

这是两种完全不同的思想,二分查找是分治思想,我们可以使用递归或者循环的方式来做。而很多递归问题也不一定是分治的,因此两个完全不是一回事。

2.1、循环解决二分查找

​ 代码如下:

public 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;
}

​ 对于上述的代码,还有可以优化的地方,其中有一个很重要的问题没有处理。

​ 在计算机中,除的效率非常低,一般可以用移位来代替,也就是

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

​ 除此之外,还有一个问题,假如low和high很大的话,low + high可能会溢出。因此我们可以这么写:

int mid = low + ((high - low) >> 1)

​ 为什么要加上这个括号呢?是因为移位运算符>>优先级比加减要低,所以如果不加括号的话,low + (high - low) >> 1的实际结构其实是这样的:(low + (high - low)) >> 1。很明显不是我们希望的结果,需要再加个括号更换运算顺序。

​ 最终的代码如下:

public int binarySearch(int[] array, int low, int high, int target) {
    while(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;
}

2.2、递归解决二次查找

​ 递归解决就是将待查的目标值与中间值对比,如果相等,则mid就是我们需要的值,如果mid > target,则继续在左半边调用二分查找继续找,如果mid < target,则就在右半边调用二分查找继续找,直到得到最终结果。

​ 代码如下:

public int binarySearch (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.3、元素中有重复的二分查找

​ 假如在上述的基础上,元素存在重复,如果重复则找左侧第一个,该怎么做呢?

​ 这里的关键是找到目标结果之后不是返回而是继续向左侧移动,最简单的方法就是找到相等位置向左使用线性查找,直到找到相应的位置。

public int search(int[] nums, int target) {
    if (nums == null || nums.length == 0)
        return -1;
    int left = 0;

    if (nums[0] == target) {
        return 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--;
            return mid + 1;
        }
    }
    return -1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Molche

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值