【读书笔记】二分查找

253 阅读2分钟

下面根据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,7mid = l+(r-l)/2 == 3

  • 边界收缩时,边界不会取到右边的mid右边的数。
    • right = mid-1 == 2;
    • left = mid == 3;

(3) >>>

在 Java 中,>>>叫无符号右移。取中间数的时候,都写成int mid = (left + right) >>> 1int mid = (left + right + 1) >>> 1

这是因为无符号右移>>>在对操作数右移以后,不论这个数是正数还是负数,高位一律补 0。

  • 无符号右移即使在left + right发生溢出以后,得到的结果依然正确
  • 并且位运算比除法运算更直接,因此更快。
  • 借鉴:jdk中Arrays.binarySearch()
  • Python 在left + right整型越界的时候,直接转为long,因此不会得到负数。