二分查找的思路
二分查找也称为折半查找(binary search)
,适用于顺序存储结构的线性表
,且表中元素是有序排列
的情况。对于一个顺序排列的数组而言,当查找区间有不止一个元素的时候
(即左边界不超过右边界),每次循环取当前区间中间的元素与目标值比较,根据以下三种比较结果判断跳出循环或更新左右边界:
- 目标值 = 中间元素
此时正好在在当前区间的中间找到目标值,跳出循环。 - 目标值 > 中间元素
表示此时目标值在以中间元素为界的右区间,需要移动左边界以在右区间继续进行查找。 - 目标值 < 中间元素
表示此时目标值在以中间元素为界的左区间,需要移动右边界以在左区间继续进行查找。
算法复杂度
由于二分查找的适用场景为顺序表,因此空间复杂度为
O
(
1
)
O(1)
O(1)。
假设数组中有n个元素,且为顺序排列,如果进行进行二分查找过程如下:
- Step 1:在长度为n的区间中查找
- Step 2:在长度为 n / 2的区间中查找
- Step 3:在长度为 (n / 2) / 2 的区间中查找
- ……
- Step x:在长度为 n / (2 ^ (x - 1)) 的区间中查找
由此可见,若在最坏情况
下经过x次查找寻找到答案,最终区间长度应为1,即:
n
/
2
x
−
1
=
1
n / 2^{x - 1} = 1
n/2x−1=1
x
=
l
o
g
2
n
x = log_2n
x=log2n因此,二分查找的时间复杂度为
O
(
l
o
g
2
n
)
O(log_2n)
O(log2n)。
几种不同的二分查找
1. 普通二分
假设一个已经排好序的数组num中有n个元素,t为需要查找的目标值。
int binary_search(int t) {
int l = 0, r = n - 1;
while (l <= r) {
int mid = (l + r) / 2;
if (num[mid] == x) {
return 1;
} else if (x > num[mid]) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return 0;
}
在缩减查找区间
的时候,需要注意的是,每次都略过了mid这个中间元素。这是因为在前面的判断中已经确定了num[mid]不是我们需要的值,所以在下一次循环查找中,不需要将其包含在查找区间中。
2. 二分答案
在实际的问题中,有时二分的对象未必是答案,而是需要将二分的对象经过某些特殊运算得到的值进行判断,由此得到满足条件的解。此类型问题的答案通常具有单调性
,且多数为边界值解(如求最大的最小值等),这类问题被统称为二分答案问题
。
对于二分答案类型问题的思路,可以从二分的对象入手进行推导:
- 首先对于一组样例输入数据,找到符合条件的目标边界值。
- 取该值的相邻值进行判断,如果能满足条件则该侧均能满足条件(1),不满足条件则该侧均不能满足条件(0)。
- 据此可以建立起对应的
代码模型
,根据不同输入样例求解。
因此,二分答案的结果可以近似为两种类型:00001111型和11110000型。
两种类型的代码模版如下:
-
00001111型
while (l != r) { int mid = (l + r) / 2; if (func(mid) >= t) { // t为目标查找数 r = mid; } else { l = mid + 1; } } return r; // return l;
注意:
- 移动右边界时,mid可能正好是答案,在新的查找区间中
需要
包含mid。 - 移动左边界时,经过前面判断可知mid不是答案,所以在新的查找区间中
不需要
包含mid。 - 最终左边界 = 右边界,查找区间不存在,此时l和r均为答案,二者任选其一即可。
- 移动右边界时,mid可能正好是答案,在新的查找区间中
-
11110000型
while (l != r) { int mid = (l + r + 1) / 2; if (func(mid) <= t) { // t为目标查找数 l = mid; } else { r = mid - 1; } } return l; // return r;
注意:
- 在查找区间只剩下两个元素的时候(10),如果仍然使用
m
i
d
=
(
l
+
r
)
/
2
mid = (l + r) / 2
mid=(l+r)/2来计算mid,
m
i
d
=
l
mid = l
mid=l将会永远成立,查找区间不会缩小,导致
死循环
。所以需要使用 m i d = ( l + r + 1 ) / 2 mid = (l + r + 1) / 2 mid=(l+r+1)/2向上取整来计算mid的值。 - 移动左边界时,mid可能正好是答案,在新的查找区间中
需要
包含mid。 - 移动右边界时,经过前面判断可知mid不是答案,所以在新的查找区间中
不需要
包含mid。 - 最终左边界 = 右边界,查找区间不存在,此时l和r均为答案,二者任选其一即可。
- 在查找区间只剩下两个元素的时候(10),如果仍然使用
m
i
d
=
(
l
+
r
)
/
2
mid = (l + r) / 2
mid=(l+r)/2来计算mid,
m
i
d
=
l
mid = l
mid=l将会永远成立,查找区间不会缩小,导致
3. 小数二分
对于小数二分,推导思路与上述两种问题一样,唯一需要注意的地方是循环的条件发生了变化。因为二分的对象为浮点型数据
,查找区间应为开区间
而不是闭区间了,所以不需要再判断左右边界是否相等,而是通过判断左右边界之间差值的精度(如r - l > 0.00001),近似的判断左右边界是否重合。