二分查找可能是最早接触的查找方式,时间复杂度仅为O(lg2(n))级别,而且易于理解
但是理解了是一回事,动手写起来又是一回事,绝大部分人会在是否取等以及边界值+1的问题上栽倒很多次,就像《只狼》拼刀,看通关视频都很容易,苇名一心也不过尔尔,结果自己打的时候上来就在新手村先死个20次
先附上原码:
int binary_search(vector<int>& nums, int target)//搜索后返回数据在数组的坐标值,如果不存在则返回-1
{
int right=nums.size()-1;//二分查找的右极限
int left=0;//二分查找的左极限
int middle=left+(right-left)/2;//中间数的坐标
int number=nums[middle];//中间的数据
while(number!=target)
{
if(number>target)
{
right=middle;
middle=left+(right-left)/2;
number=nums[middle];
}
else if(number<target)
{
left=middle+1;
middle=left+(right-left)/2;
number=nums[middle];
}
if(right==left&&number!=target)
return -1;
}
return middle;
}
接下来解释细节部分:
1.取等问题
很多人都在left和right比较大小的地方犹豫很久,这里我特意绕开了这个判断,直接在循环中省去了两者比较的判断,单独列出一个if作为判断条件:既然left和right已经取等,说明查找也到了最后一步,如果再没有合适的值,就直接退出即可
2.+1问题
这里是绝大部分人在第一次尝试的时候绝对会栽跟头的地方
由于计算机计算数据的时候采用2进制编码数据,对于无法整除数据的情况,采用的是“向0取整”,即:结果大于0的小数,仅保留整数部分,结果小于0的小数,采用进一的方式取整
由于数组的下标均为非负数,所以二分查找的平均数也一定为正数,因此,每次取整的时候,数据会偏小,也就是left会更新为一个较小的整数
例如:当面临要查找的数据恰好处于下标7上,此时left=6,right=7,每次对middle和left进行更新的时候,left都因为向下取整的问题而无限循环……
这时候,我们需要舍弃:既然之前,middle上的数据已经不是我们需要的了,那就让left在顶替middle的时候自动+1
但由于right没有向下取整的问题,所以不需要进行对应的-1操作,如果进行了-1操作,反而会出现新的问题:
例如,我们在数组[5]当中查找数字-2,此时left,middle,right均为0,在第一轮循环的时候,由于需要对right进行-1,这时就会出现数组下标负溢出的情况(right=-1)
3.细节问题
由于我们需要查找的数组可能会很大,大到下标即将溢出int型的范围,这时候我们就要小心了:middle在采用(right+left)/2的计算模式下, 可能会溢出
所以我们修改了middle的计算方式,将其改为一种较为安全的计算方法,保证计算的过程中,不会出现溢出的可能