二分查找算法(Binary Search Algorithm)是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到元素。
二分查找算法的时间复杂度为O(log n),其中n是数组的长度。这使得二分查找算法比线性查找(时间复杂度为O(n))快得多,特别是当数据量很大时。
算法步骤
- 初始化:设置两个指针,
low
指向数组的第一个元素,high
指向数组的最后一个元素。 - 循环条件:当
low
小于等于high
时,执行循环。 - 计算中间位置:
mid = low + (high - low) / 2
(或mid = (low + high) / 2
,但前者可以防止low + high
溢出)。 - 比较:将中间位置的元素与要查找的元素进行比较。
- 如果中间元素正好是要查找的元素,则搜索过程结束。
- 如果要查找的元素大于中间元素,则调整
low
为mid + 1
,继续在数组的右半部分查找。 - 如果要查找的元素小于中间元素,则调整
high
为mid - 1
,继续在数组的左半部分查找。
- 循环结束:如果
low
大于high
,则表示数组中不存在要查找的元素。
二分查找算法不一定需要数组具有有序性,而是需要数据具有二分性。
二分查找的“二分性”是其核心特性,它指的是在有序数组中,通过不断将搜索区间分成两半来缩小搜索范围,直到找到目标值或确定目标值不存在为止。这种将问题规模减半的策略,是二分查找高效性的关键所在。
具体来说,二分查找的二分性体现在以下几个方面:
-
搜索区间二分:在每一次迭代中,算法都会计算当前搜索区间的中间位置,并根据中间元素与目标值的大小关系,将搜索区间划分为两个子区间。其中一个子区间包含可能的目标值(根据比较结果决定是哪个子区间),而另一个子区间则不包含目标值,因此可以被排除。
-
时间复杂度二分:由于每次迭代都将搜索区间减半,因此二分查找的时间复杂度是O(log n),其中n是数组的长度。这意味着随着数组长度的增加,算法所需的时间增长速度远慢于线性搜索(时间复杂度为O(n))。
-
递归与迭代二分:二分查找可以通过递归或迭代两种方式实现。递归实现更加直观,但可能由于深度过深而导致栈溢出;迭代实现则避免了这个问题,但代码可能相对复杂一些。不过,无论是递归还是迭代,其本质都是利用了二分性来缩小搜索范围。
-
条件二分:在二分查找的过程中,始终维护着两个条件(或说是指针、索引),即搜索区间的左右边界。这两个条件(或边界)将数组划分为三个部分:已搜索过的左半部分、当前正在搜索的中间部分(虽然实际上只关注中间的一个元素)、以及尚未搜索的右半部分。随着迭代的进行,已搜索过的部分逐渐增大,而尚未搜索的部分逐渐减小,直到找到目标值或确定目标值不存在。
综上所述,二分查找的二分性体现在其不断将搜索区间二分、时间复杂度二分、递归与迭代二分以及条件二分等方面。这种二分性使得二分查找成为处理有序数组搜索问题的高效算法之一。
题目描述
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
对于该题,我们首先便能够轻易的想出暴力解法:遍历整个数组找到目标数。
但是这种遍历的方法一次仅能确定一个数,时间复杂度为O(n),效率较低。
为了在给定的有序整型数组 nums
中搜索目标值 target
,并返回其下标,我们可以使用二分查找算法。二分查找算法的基本思想是,在每一步迭代中,将搜索区间分成两半,判断目标值 target
与中间元素的大小关系,从而决定是继续在左半部分搜索,还是在右半部分搜索。这样每次都可以排除一半的元素。
原理详解
-
初始化:
- 设定两个指针(或索引),
left
指向数组的第一个元素,right
指向数组的最后一个元素。这两个指针定义了当前搜索的区间范围。
- 设定两个指针(或索引),
-
循环条件:
- 当
left
小于等于right
时,继续搜索。这个条件保证了搜索区间内至少有一个元素。
- 当
-
计算中间位置:
- 为了避免在计算中间位置时可能出现的整数溢出问题(特别是当数组长度非常大时),通常使用
mid = left + (right - left) // 2
而不是mid = (left + right) // 2
。 - 这个中间位置将当前搜索区间分为两个子区间,一个包含从
left
到mid - 1
的元素,另一个包含从mid + 1
到right
的元素。
- 为了避免在计算中间位置时可能出现的整数溢出问题(特别是当数组长度非常大时),通常使用
-
比较与目标值的大小:
- 将中间位置的元素
nums[mid]
与目标值target
进行比较。 - 如果
nums[mid] == target
,则找到了目标值,返回mid
。 - 如果
nums[mid] < target
,说明目标值在当前中间位置的右侧(或说是在mid + 1
到right
的范围内),因此更新left = mid + 1
,继续在右半部分搜索。 - 如果
nums[mid] > target
,说明目标值在当前中间位置的左侧(或说是在left
到mid - 1
的范围内),因此更新right = mid - 1
,继续在左半部分搜索。
- 将中间位置的元素
-
循环结束:
- 如果循环结束时仍未找到目标值(即
left
大于right
),则说明目标值不在数组中,返回-1
。
- 如果循环结束时仍未找到目标值(即
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0,right = nums.size()-1,mid;
while(left<=right){
mid = (left+right)/2;
if(nums[mid] < target){
left = mid+1;
}
else if(target<nums[mid]){
right = mid-1;
}
else{
return mid;
}
}
return -1;
}
};