前言
本文介绍了二分查找法的使用方法及其思想原理,相应的代码使用的是Java语言
一、二分查找基本概念
1、前提条件
二分查找也叫折半查找,它的前提条件是被搜索的序列是有序的(升序或降序),且元素是不重复的。
例如:{2,5,8,10,56,94},我们要查找当中的某一个数,就可以使用二分查找法
而{1,3,2,6,9,4},{1,1,5,3,6,6,9}这样的序列就不可以使用二分查找,前者不是有序的,后者元素不是唯一的。
2、基本思想
这里先提出一个小游戏,你在纸上写一个100以内的正整数,我来猜,问怎么猜最快速有效?
答案是使用二分查找的思想最快速有效,如果你写的是35,那么我们先猜50(100)的一半,被告知“大了”,再猜25(50的一半),被告知“小了”,再猜37(50和25中间的哪个数,向下取整),大了,再猜31,小了,再猜34,小了,再猜它们的中间数35,我们就得到正确答案了。用这样的方法,100以内的任何正整数,我们最多只要猜7次,就能得到正确答案。
总结一下其基本思想:在有序表中,我们取中间元素作为被比较对象,若目标值(我们想要查找的值)刚好与中间值相等,则查找成功;若目标值小于中间值,则在中间值的左半边继续查找;若目标值大于中间值,则在中间值的又半边继续查找。不断重复上述过程,直到查找成功。或所有查找区域均无记录,则查找失败。
思想很简单,很容易理解,但真正代码实现时涉及到很多细节问题,包括循环的判断条件,所选查找区域的边界如何处理,因此很多人就出现了,一看就会,一写就废的问题。
二、二分查找的实现(Java)
1.力扣题目引入
这里我们用力扣官网的题目来举例实现二分查找
力扣题目链接
题目描述如下:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
提示:
1、你可以假设 nums 中的所有元素是不重复的。
2、n 将在 [1, 10000]之间。
3、nums 的每个元素都将在 [-9999, 9999]之间。
这道题目的前提是有序数组且没有重复元素,刚好符合二分查找。二分查找的逻辑简单,但涉及到一些边界问题,在用代码实现的时候就很容易乱写,得不到想要的效果。可以按照下面的步骤来实现二分查找。
2.详细步骤
确定区间
第一步我们要定义一个区间,假设target(目标值)就在这个区间中,我们将通过循环,来逐渐缩小这个区间最终找到target。
通常定义该区间是一个左闭右闭的区间,即[left,right]。实际上也可以是[left,right),但其它区间可能不太行,试一下就会发现有个别值,始终无法查找到。
不同的区间定义方式会导致不同的边界处理。
[left,right]写法
这种写法我们认为,target在左闭右闭区间[left,right]中。我们将通过循环,来逐渐缩小这个区间最终找到target。
(1)循环条件为while (left <= right),因为最初定义的区间是左闭右闭的,所以当left=right时也是有意义的,此时区间里只有一个值,下一次循环我们判断它是不是 target,若是则查找成功,返回对应的下标;若不是,查找失败(序列中没有目标值),返回-1.
(2)二分法要求我们找到序列的最中间的数,来作为被比较对象。这里我们设中值的下标为middle
且middle=(left+right)/2;为了避免可能出现的越界,我们把计算middle的值的代码改为:middle=left+(right-left)/2;它和middle=(left+right)/2是等价的。
(3)每次循环都要比较target与middle
若target=middle,那么目标值刚好被找到;
若target>middle,说明target不在左半区间,所以left要赋值为middle+1(target>middle,所以middle一定不是目标值,闭合区间内显然不需要包括middle,所以left=middle+1);
若target<middle,说名target不在右半区间,所以right赋值为middle-1(理由同上);
代码实现(Java)
class Solution {
public int search(int[] nums, int target) {
int left=0;
int right=nums.length-1;
int middle=left + ((right - left) / 2);
//二分查找
while(left<=right) {
if(target==nums[middle]) {
return middle;
}
else if(target>nums[middle]) {
left=middle+1;
}
else{
right=middle-1;
}
middle=left + ((right - left) / 2);
}
//没有找到
return -1;
}
}