二分查找
思想
二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0。
示例图
O(logn) 惊人的查找速度
所以,经过了k次区间缩小操作,时间复杂度就是O(k)。通过n/2 k =1,我们可以求得k=log 2 n,所以时间复杂度就是O(logn)
代码实现
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (a[mid] == value) {
return mid;
} else if (a[mid] < value) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
适用条件
- 二分查找依赖的是顺序表结构,简单点说就是数组。
- 二分查找针对的是有序数据。
- 二分查找只能用在插入、删除操作不频繁,一次排序多次查找的场景中。针对动态变化的数据集合,二分查找将不再适用。
- 数据量太大也不适合二分查找。
- 二分查找的底层需要依赖数组这种数据结构,而数组为了支持随机访问的特性,要求内存空间连续,对内存的要求比较苛刻。
注意点
-
循环退出条件
- 注意是 low<=high ,而不是 low<high 。
-
mid 的取值
- 如果 low 和 high 比较大的话,两者之和就有可能会溢出。
- 将 mid 的计算方式写成 low+(high-low)/2
- low+((high-low)>>1) (注意位运算的计算权重,这里需加括号)
-
low 和 high 的更新
- low=mid+1 , high=mid-1 。注意这里的 +1 和 -1 ,如果直接写成 low=mid 或者high=mid,就可能会发生死循环。
二分算法的变体
变体一:查找第一个值等于给定值的元素
a[mid] 跟要查找的 value 的大小关系有三种情况:大于、小于、等于。
对于a[mid]>value的情况,我们需要更新high= mid-1;
对于a[mid]<value的情况,我们需要更新low=mid+1 。
如果 mid 等于 0 ,那这个元素已经是数组的第一个元素,那它肯定是我们要找的;如果 mid 不等于 0 ,但 a[mid] 的前一个元素 a[mid-1] 不等于 value ,那也说明 a[mid] 就是我们要找的第一个值等于给如果经过检查之后发现 a[mid] 前面的一个元素 a[mid-1] 也等于 value ,那说明此时的 a[mid] 肯定不是我们要查找的第一个值等于给定值的元素。那我们就更新 high=mid-1 ,因为要找的元素肯定出现在 [low, mid-1] 之间。
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else if (a[mid] < value) {
low = mid + 1;
} else {
if ((mid == 0) || (a[mid - 1] != value)) return mid;
else high = mid - 1;
}
}
return -1;
}
变体二:查找最后一个值等于给定值的元素
只需要将a[mid]相等的时候,继续判断,当:
- 在数组末尾的时候,肯定已经在相等连续值的末尾了
- 连续相等的数后面还有数,只能和后面的数不相等就行了(数组已经有序)
if ((mid == n - 1) || (a[mid + 1] != value)) return mid;
else low = mid + 1;
变体三:查找第一个大于等于给定值的元素
注意判断条件,可以等于 value,两种情况:
- 如果mid为数组第一个元素,则它前面没有元素,则为第一个
- 如果前边有数据且小于a[mid],则也符合条件
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] >= value) {
if ((mid == 0) || (a[mid - 1] < value)) return mid;
else high = mid - 1;
} else {
low = mid + 1;
}
}
return -1;
}
变体四:查找最后一个小于等于给定值的元素
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] <= value) {
if ((mid == n-1) || (a[mid + 1] > value)) return mid;
else low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
思考?
- 如何快速定位 IP 对应的省份地址?
- 如果有序数组是一个循环有序数组,比如 4 , 5 , 6 , 1 , 2 , 3 。针对这种情况,如何实现一个求 “ 值等于给定值 ” 的二分查找算 法呢?(leetcode 33 题)