二分查找原理
二分查找的原理不难理解,即在一个有序数组中,每次将数组中间值与目标值进行比较,从而判断目标值在中间值的左边还是右边。
由于每次可排除一半的数,因此二分查找的时间复杂度为log2n。对于100万个有序数据,利用二分查找仅需搜索20次,效率非常高。mysql的B+树叶子节点的数据查找,用的就是二分查找。
代码编写
中间值
第一次写二分查找的代码时,都会有一个疑惑,那就是如果数组的个数是奇数和偶数时,中间值要不要分别处理?相信当你仔细去区分时,十有八九会被绕晕。我们完全可以不用考虑这个情况。
边界处理
二分查找的难点在于边界处理,解决了这个问题,二分查找的代码编写也就变得很简单了。下面介绍常用的两种边界规定方式,以递增数组为例。
- 左闭右开
public int searchInsert(int[] nums, int target) {
// 指针初始化,这里体现左闭右开[0, n)
int l = 0; // 左指针
int r = nums.length; // 右指针
while (l < r){
int mid = l + (r - l) / 2; // 若采用(r + l) / 2 会有超出int最大值的风险
if (nums[mid] > target){
r = mid; // 右指针等于mid。(体现右开原则)
}else if (nums[mid] < target){
l = mid + 1; // 左指针等于mid + 1。 (体现左闭原则)
}else {
return mid;
}
}
return -1;
}
开始时,我们便把nums.length
作为右指针,而数组的最大下标为nums.lenth - 1
,所以这里第一次体现了左闭右开(右指针的值我们无需考虑,因为已经越界了)。
当我们把中间值与目标值进行对比时,若中间值大于目标值,说明目标值存在于左半部分,令r = mid
。(由于是右闭,此时mid的值我们无需再考虑了,因此直接r = mid
)。若中间值小于目标值时,说明目标值存在于右半部分,令l = mid + 1
。这里为什么要+ 1
呢?因为左边是闭区间,此时mid的值我们无需考虑了,因此l = mid + 1
,跳过mid值。若中间值等于目标值,那恭喜你找到了目标值,直接返回即可。
- 左闭右闭
public int searchInsert(int[] nums, int target) {
// 指针初始化,这里体现左闭右闭[0, n - 1]
int l = 0; // 左指针
int r = nums.length - 1; // 右指针
while (l <= r){
int mid = l + (r - l) / 2; // 若采用(r + l) / 2 会有超出int最大值的风险
if (nums[mid] > target){
r = mid - 1; // 右指针等于mid。(体现右闭原则)
}else if (nums[mid] < target){
l = mid + 1; // 左指针等于mid + 1。 (体现左闭原则)
}else {
return mid;
}
}
return -1;
}
与左闭右开相比,初始时我们的右指针变为了nums.length - 1
,这也是体现了左闭右闭原则。
当中间值mid大于目标值时,这里我们就得把 r = mid - 1
,以排除mid值。
与左闭右开的while(l < r)
相比,这里判断条件为啥是while(l <= r)
呢?因为当l == r
时,[l, r)
已经是个空区间了,而[l, r]
区间还有一个值l
,需要继续做下一步的判断。
其它情况同左闭右开。