查找算法之二分查找算法
1、 概述
二分查找算法也称折半查找算法,是在有序数组
中用到的较为频繁的一种查找算法。在未接触二分查找算法时,最通用的一种做法是,对数组进行遍历,跟每个元素进行比较,即顺序查找。二分查找较顺序查找更优,因为这种算法每一次比较都使查找范围缩小一半。
二分查找是一种基于比较目标值和数组中间元素的教科书式算法。
- 如果目标值等于中间元素,则找到目标值。
- 如果目标值较小,继续在左侧搜索。
- 如果目标值较大,则继续在右侧搜索。
2、算法思想
二分查找算法是建立在有序数组
基础上的。算法思想为:
- 查找过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则查找过程结束;
- 如果某一待查找元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟第 1 点一样从中间元素开始继续进行查找。
- 如果在某一步骤数组为空,则代表找不到。
例子: 有序数组nums[-1,0,3,5,9,12],目标值即要查找的元素 target = 9;
算法实现思路如下:
- 初始化起点位置和终点位置,声明数组中间位置变量。
var left = 0; // 起点数组索引
var right = nums.length - 1; // 终点数组索引
var middle = 0; // 数组中间位置
-
当
left <= right
:-
求中间middle索引:
middle = Math.floor(left + (right - left) / 2);
(建议使用这种方法求中间索引,可以防止 left + right 溢出(超出整数范围))。 -
比较中间元素
nums[middle]
和目标值target
- 如果
target = nums[middle]
,返回middle
。 - 如果
target > nums[middle]
,则在右侧继续搜索,把数组中间位置加1作为下一次计算的起点left = middle + 1
。 - 如果
target < nums[middle]
,则在左侧继续搜索,把数组中间位置减1作为下一次计算的终点right = middle - 1
。
- 如果
-
3、算法实现
题目描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例2:
输入: nums = [-1, 0, 3, 5, 9, 12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 - 1
方法1:使用非递归方式查找
/*二分查找:非递归方式查找*/
var search = function (nums, target) {
var left = 0; // 起点数组索引
var right = nums.length - 1; // 终点数组索引
var middle = 0; // 数组中间位置
while (left <= right) {
middle = Math.floor(left + (right - left) / 2); // 防止计算时溢出
if (target > nums[middle]) {
left = middle + 1; // 继续在右侧搜索
} else if (target < nums[middle]) {
right = middle - 1; // 继续在左侧搜索
} else {
return middle; // 找到目标值,返回目标值在数组中的索引
}
}
return -1; // 没有找到目标值
};
var ary1 = [-1, 0, 3, 5, 9, 12];
var target1 = 9;
console.log(search(ary1, target1)); // 4
方法2:使用递归方式查找
/* 二分查找:递归方式查找 */
var search = function (nums, target, left, right) {
if (left > right) {
return -1; // 没有找到目标值
}
var middle = Math.floor(left + (right - left) / 2); // 防止计算时溢出
if (target > nums[middle]) {
left = middle + 1;
return search(nums, target, left, right); // 继续在右侧搜索
} else if (target < nums[middle]) {
right = middle - 1;
return search(nums, target, left, right); // 继续在左侧搜索
} else {
return middle; // 找到目标值,返回目标值在数组中的索引
}
};
var ary1 = [-1, 0, 3, 5, 9, 12];
var target1 = 9;
console.log(search(ary1, target1, 0, ary1.length - 1)); // 4
4、复杂度分析
- 时间复杂度:O(logN)。
- 空间复杂度:O(1)。
5、案例:搜索插入位置
题目描述:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
解题思路:
标签:二分查找
- 如果该题目暴力解决的话需要 O(n) 的时间复杂度,但是如果二分的话则可以降低到 O(logN) 的时间复杂度
- 整体思路和普通的二分查找几乎没有区别,先设定左侧下标 left 和右侧下标 right,再计算中间下标 mid
- 每次根据 nums[mid] 和 target 之间的大小进行判断,相等则直接返回下标,nums[mid] < target 则 left 右移,nums[mid] > target 则 right 左移
- 查找结束如果没有相等值则返回 left,该值为插入位置
- 时间复杂度:O(logN)
二分查找的思路不难理解,但是边界条件容易出错,比如 循环结束条件中 left 和 right 的关系,更新 left 和 right 位置时要不要加 1 减 1。
代码如下:
var searchInsert = function (nums, target) {
var left = 0; // 起点数组索引
var right = nums.length - 1; // 终点数组索引
var middle = 0; // 数组中间位置
while (left < right) {
middle = Math.floor(left + (right - left) / 2); // 防止计算时溢出
if (target > nums[middle]) {
left = middle + 1; // 继续在右侧搜索
} else if (target < nums[middle]) {
right = middle - 1; // 继续在左侧搜索
} else {
return middle; // 找到目标值,返回目标值在数组中的索引
}
}
// 此时有 left == right , 目标值不在数组中
if (target > nums[left]) {
return left + 1; // 返回 target 将会被按顺序插入的位置
} else {
return left; // 返回 target 将会被按顺序插入的位置
}
};