二分查找又名折半查找,是在有序序列中高速寻找所需元素的算法。
以下图为例。
以该寻找7为例,自然可以看到折半查找的高效之处,序列总数相差的越大,二分查找相对于整体遍历的效率相差的就越多,相对于遍历的O(n)
的时间复杂度,折半查找的时间复杂度为log2(n)
。
当然本次教程不单单说这些,而是讨论下二分查找的细节问题,二分查找的终止条件。
感谢大佬的文章让我学到了很多东西
二分查找从入门到入睡
二分查找是需要计算中间值下标的:
如果你用的是mid = (low + right)/ 2
那么恭喜你!
你和JDK开发者一个水平!
P : 我写错,大佬写作 =>
我 = 大佬
咳咳,不扯皮了
到 2006 年的时候,Joshua Bloch 才知道「编程珠玑」中的二分查找实现存在上述整数溢出的问题,此时距离该书出版已经过去了 21 年,直到那时,同样的 bug 在他实现的 JDK 的
binarySearch 里也已经存在了 9 年之久。就因为中间值下标的计算语句是mid = (low + right)/ 2
我举个例子,low = 2
;
high = 2^32 - 1;
虽然二者都没超过正整数的最大值,但是他们的和却超出正整数最大值上限
,导致了提前溢出。
提前溢出的处理方法
mid = left + (lright - low)/ 2
mid = (low + high) >>> 1
;下面以搜索‘4’为例
3.1. low <= high
(相错中止)
while(low <= high){
}
// 模版一「一般」情形4: 小于
class Solution {
public int search(int[] nums, int target) {
int l = 0, r = nums.length - 1;
while(l <= r){
int c = l + (r - l) / 2;
if(nums[c] < target) l = c + 1; // #1 更新后l左侧元素「必」小于target
else r = c - 1; // #2 更新后r右侧「必」大于等于target
}
return r; // 处理: 相等/刚好小于/不存在
}
}
3.2. low < high
(相等中止)
while(low < high){
}
// 模版二「相等返回」写法
class Solution {
public int search(int[] nums, int target) {
int l = 0, r = nums.length;
while(l < r){
int c = l + (r - l) / 2;
if(nums[c] == target) return c; // 找到目标值直接返回
else if(nums[c] < target) l = c + 1; // #1 更新后l左侧元素「必」小于target
else r = c; // nums[c] > target #2 更新后r及其右侧「必」大于target
}
return -1;
}
}
3.1. low + 1 < high
(相邻中止)
while(low + 1 < high){
}
// 模版三「相等返回」写法
class Solution {
public int search(int[] nums, int target) {
int l = -1, r = nums.length;
while(l + 1 < r){
int c = l + (r - l) / 2;
if(nums[c] == target) return c; // 找到目标值直接返回
else if(nums[c] < target) l = c; // #1 更新后l及其左侧元素「必」小于target
else r = c; // nums[c] > target #2 更新后r及其右侧「必」大于target
}
return -1;
}
}