二分思想
二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都用过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0。
假设我们要寻找19。
这里给出二分查找的代码实现:
int binary_search(int* arr,int capacity,int elem){
int low = 0;
int high = capacity -1;
while(low <= high){
int mid = low+ ((high-low)>>1);// 防止两个数相加溢出
if(arr[mid] == elem){
return mid+1;
}else if(elem > arr[mid]){
low = mid+1;
}else{
high = mid-1;
}
}
return -1;
}
分析:
我们假设数据规模为n,每次查找后数据都会缩小为原来的一半,最坏的情况直到区间为空,才停止。
k的值就是总共缩小的次数,而每一次缩小操作只涉及两个数据的大小比较,所以经过了k次区间缩小操作,时间复杂度就是O(k)。当n/(2^k) = 1的时候,k =log2n,所以时间复杂度是O(logn)。
另外,它不需要额外的空间,因此空间复杂度是O(1)。
适用场景
首先二分查找依赖的数据结构是数组。
因为数组支持随机访问,因此在访问数据的时候,才能做到O(1)。如果你是使用链表,我们第一次找到中间结点的时间复杂度是O(n),第二次是O(n/2),直到为1,次数k = log2n,这是一个等比数列求和,可以求出时间复杂度是O(n)。
其次,二分查找针对有序数据
对于数据来说,必须有序。我们在维护一个数组的时候,如果维护的是一组静态的数据,没有频繁地插入和删除,那么我们可以在使用快速排序之后,多次二分查找,这样排序的成本可被均摊,二分查找的边际成本就会比较低。
但是,如果我们针对的是一组动态的数组,有频繁地插入和删除,那么每一次插入,我们都需要数据搬移,再使用二分查找,那么成本是很高的。
所以二分查找只能够用在插入、删除操作不频繁,一次排序多次查找的场景中。针对动态变化的数据集合,二分查找不再使用,我们应该使用二叉排序数。
再次,二分查找不适用数据量小的规模。
如果要处理的数据很小,比如只有10个,那么遍历更快。
最后,数据太大也不适合
因为二分查找依赖数组,数组需要连续的空间,假设你有1GB的数据,那么你要在内存中开辟一个1GB的连续的空间。连续这个词就限制了应用。
二分查找的变体
1.查找第一个值等于给定值的元素
上一个实现的情况是基于没有重复的,如果现在有这样一个数组:
我们要查找第一个等于8的位置,要怎么做?当我们找到第一个等于8的元素的时候,只要判断一下它的前一个是否为8就行了,如果是的话,说明我们找到的8不是第一个,所以继续查找。这里给出代码的实现:
int binary_search_first(int* arr,int capacity,int elem){
int low = 0;
int high = capacity - 1;
while(low<=high){
int mid = low + ((high-low)>>1);
if(elem == arr[mid]){
if(elem != arr[mid-1]){
return mid+1;
}else{
high = mid-1;
}
}
else if(elem > arr[mid]) low = mid+1;
else high = mid-1;
}
return -1;
}
2.查找最后一个值等于给定值的元素
其实跟前面一样,只要判断找到的值的下一个是否等于给定值就行了,这里直接给出代码:
int binary_search_last(int* arr,int capacity,int elem){
int low = 0;
int high = capacity -1;
while(low<=high){
int mid = low + ((high-low) >> 1);
if(elem == arr[mid]){
if(arr[mid+1] != elem){
return mid+1;
}else{
low = mid+1;
}
}else if(elem > arr[mid]){
low = mid+1;
}else{
high = mid-1;
}
}
return -1;
}
3.查找第一个大于等于给定值的元素
实际上,我们只要在前面代码的基础上进行小的改动就可以了,代码如下:
int bsearch_large(int* arr,int capacity,int elem){
int low = 0;
int high = capacity -1;
while(low <= high){
int mid = low + ((high - low) >> 1);
if(arr[mid] >= elem){
if(mid == 0 || arr[mid-1] < elem) return mid+1;
else high = mid-1;
}else{
low = mid+1;
}
}
return -1;
}
4.最后一个小于等于给定值的元素
int bsearch_small(int* arr,int capacity,int elem){
int low = 0;
int high = capacity - 1;
while(low <= high){
int mid = low + ((high - low) >> 1);
if(arr[mid] <= elem){
if(mid == capacity-1 || arr[mid+1]>elem) return mid+1;
else low = mid +1;
}else{
high = mid-1;
}
}
return -1;
}