这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战
二分法
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。注意必须要是有序排列,但有一种特殊情况可以不必须有序排列,即前一节介绍的商品选取,从一堆标准重量为10的商品中查找出唯一的次品,这种特殊的数据情况也可以使用二分查找。
寻找一个数(基本的二分搜索)
这个场景是最简单的,可能也是大家最熟悉的,即搜索一个数,如果存在,返回其索引,否则返回 -1。
function numSearch(nums, target) {
let left = 0;
let right = nums.length - 1; // 注意
while(left <= right) { // 注意
let mid = (right + left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1; // 注意
else if (nums[mid] > target)
right = mid - 1; // 注意
}
return -1;
}
注意的点
- 我们的循环条件中包含了
left == right的情况,则我们必须在每次循环中改变left和right的指向,以防止进入死循环
循环终止的条件包括:
- 找到了目标值
left > right(这种情况发生于当left, mid, right指向同一个数时,这个数还不是目标值,则整个查找结束。)left + ((right -left) >> 1)其实和(left + right) / 2是等价的,这样写的目的一个是为了防止(left + right)出现溢出,一个是用右移操作替代除法提升性能。left + ((right -left) >> 1)对于目标区域长度为奇数而言,是处于正中间的,对于长度为偶数而言,是中间偏左的。因此左右边界相遇时,只会是以下两种情况:left/mid,right(left, mid 指向同一个数,right指向它的下一个数)left/mid/right(left, mid, right 指向同一个数)- 即因为
mid对于长度为偶数的区间总是偏左的,所以当区间长度小于等于2时,mid总是和left在同一侧。
二分查找左边界
利用二分法寻找左/右边界是二分查找的一个变体,应用它的题目常常有以下几种特性之一:
- 数组有序,但包含重复元素
- 数组部分有序,且不包含重复元素
- 数组部分有序,且包含重复元素
// 查找左边界
function searchLeft( nums, target) {
let left = 0;
let right = nums.length - 1;
while (left < right) {
let mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
return nums[left] == target ? left : -1;
}
注意的点
- 循环条件:
left < right - 中间位置计算:
mid = left + ((right -left) >> 1) - 左边界更新:
left = mid + 1 - 右边界更新:
right = mid - 返回值:
nums[left] == target ? left : -1
二分查找右边界
function searchRight(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left < right) {
int mid = left + parseInt((right - left) /2) + 1;
if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid;
}
}
return nums[right] == target ? right : -1;
}
注意的点
中间位置的计算变了,我们在末尾多加了1。这样,无论对于奇数还是偶数,这个中间的位置都是偏右的。