在开发中,我们常常会需要查找某个顺序存储结构
中的内容时,我们常常会直接使用Javascript
中已经封装好的函数去查找或者辅助查找。
但是也由于封装的特性,我们对这种封装好的函数,比较难以控制其复杂度,包括其内部执行语句的庸余部分难以把控。所以,在算法中,诞生了二分查找这个算法概念。
二分查找(Binary Search)也叫作折半查找。二分查找有两个要求,一个是数列有序,另一个是数列使用顺序存储结构(比如数组)。
二分查找
的实现原理非常简单,首要条件是要有一个有序的列表或者是一个顺序存储结构,如:数组,有序树状结构等。
但是如果没有,则该怎么办?可以使用排序算法进行排序,给二分查找创建先要条件。
以升序数列
为例,在数组 nums
中寻找目标值 target,对于特定下标 i,比较 nums[i]
和 target
的大小:
如果 nums[i] = target
,则下标 i
即为要寻找的下标;
如果 nums[i] > target
,则 target
只可能在下标 i
的左侧,则需要将下次查找的结束下标 - 1;
如果 nums[i] > target
,则 target
只可能在下标 i
的右侧,则需要将下次查找的开始下标 + 1;
根据上面描述的理论,不难理解。
二分查找的做法是,定义查找的范围 [left,right]
,初始查找范围是整个数组。每次取查找范围的中点 mid
,比较 nums[mid]
和 target
的大小,如果相等则 mid
即为要寻找的下标,如果不相等则根据 nums[mid]
和 target
的大小关系将查找范围缩小一半。
图解如下:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
本题取自 leetcode 算法基础
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
// 方法一:循环比对, 代码简洁明了,但在数据量大且需要自定义排查逻辑时,不太合适。 适用于简单数组筛查或非顺序数据结构筛查
return nums.indexOf(target)
return nums.findIndex(target)
// 方法二: 二分查找,代码
let left = 0, right = nums.length - 1
while(left <= right) {
const mid = Math.floor((right + left) / 2) | Math.floor((right - left) / 2) + left
const curVal = nums[mid]
if(curVal === target) return mid
if(curVal > target) right = mid - 1
else left = mid + 1
}
return -1
};
注意事项
: 这里我们可能会有疑惑的地方,就是中间值的取值了。由于left和right都代表的是元素的下标,所以在取中间值的时候,按道理只需right/2 | (right - left) / 2就行,但是这求的是 right下标值的中间值,并非要求的数组中间值下标。 所以需要再加上left的原本的下标值。即: ( left + right ) / 2 或 ( right - left ) / 2 + left
。
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
升序
排列。升序
排列。本题取自 leetcode 算法
输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true
示例 2:
输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
输出:false
/**
* @param {number[][]} matrix
* @param {number} target
* @return {boolean}
*/
var searchMatrix = function(matrix, target) {
// 常规循环比对
return matrix.some((item, i) => {
if(item[0] > target) return false
if(item[item.length - 1] < target) return false
return item.indexOf(target) != -1
})
// 二分查找
const getIndex = (nums = [], target = '') => {
let left = 0, right = nums.length - 1
while(left <= right) {
let mid = Math.floor((left + right)/2)
const val = nums[mid]
if(val == target) return mid
if(val > target) right = mid - 1
else left = mid + 1
}
return -1
}
for(let cur of matrix) {
if(cur.length == 0 ) continue
let start = cur[0], end = cur[cur.length - 1]
if(start <= target && end >= target && getIndex(cur, target) != -1) return true
}
return false
};
通过上述案例,相信各位卷王对 “ 二分查找 ”的概念也理解的七七八八了。 可以尝试去刷刷二分查找类型的算法题,巩固记忆吧!
点击跳转 LeetCode “ 二分查找 ” 类型题目
< CSS小技巧:filter滤镜妙用>
< JavaScript技术分享: 大文件切片上传 及 断点续传思路 >
< 每日技巧: JavaScript代码优化 >
< 每日知识点:关于Javascript 精进小妙招 ( Js技巧 ) >