下面根据while的条件,把二分查找分为两类。
一、模板一
while (left <= right)
这个思路把待查找数组分为了 3 个部分,if else有三个分支,它直接面对目标元素,在目标元素在待搜索数组中有只有 1 个的时候,可能提前结束查找。但是如果目标元素没有在待搜索数组中存在,则不能节约搜索次数;
mid所在位置;mid的左边;mid的右边;
缺点
思考的点变多了,容易增加出错率,比如:
- 返回
left还是right; - 明明已经看到了等于
target的元素,但是题目要求返回小于等于target的第 1 个元素的位置,或要求返回大于等于target的最后 1 个元素的位置的时候,一不小心会把代码写成线性查找。
二、模板二
while (left < right)
- 优点:更符合二分语义,不用去思考返回 left 还是 right,在退出循环的时候,有的时候,根据语境不正确的数都排除掉,最后剩下的那个数就一定是目标值,不需要再做一次判断。
- 缺点:理解当分支逻辑出现
left = mid的时候,要修改取中间数的行为,使其上取整。
if (check(mid)) {
// 下一轮搜索区间是 [left, mid]
right = mid;
} else {
// 剩下的区间一定是 [mid + 1, right]
left = mid+1;
}
三、取平均数
(1) 会溢出
int mid = (left + right) / 2;
这种写法在绝大多数情况下是没问题的,但是在left 和 right特别大的场景中,left + right会发生溢出,于是得到一个负数,mid 的值随之也是负数。
(2) 改进
int mid = left + (right - left) / 2;
/是整除,它的行为是向下取整,这种写法mid永远取不到数组里最右边的位置。比如当0,1,2,3,4,5,6,7,mid = l+(r-l)/2 == 3
- 边界收缩时,边界不会取到右边的
mid右边的数。right = mid-1 == 2;left = mid == 3;
(3) >>>
在 Java 中,>>>叫无符号右移。取中间数的时候,都写成int mid = (left + right) >>> 1和int mid = (left + right + 1) >>> 1。
这是因为无符号右移>>>在对操作数右移以后,不论这个数是正数还是负数,高位一律补 0。
- 无符号右移即使在
left + right发生溢出以后,得到的结果依然正确 - 并且位运算比除法运算更直接,因此更快。
- 借鉴:jdk中
Arrays.binarySearch()- Python 在
left + right整型越界的时候,直接转为long,因此不会得到负数。