浅谈二分查找
最近看了二分查找的一些内容,记录一下。不要小看二分查找,看似简单的二分查找其实是暗藏玄机的。
本文参考极客时间王争《数据结构与算法之美》
0.什么是二分查找
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列
。(摘自百度百科)
若你还是不理解,其实生活中还是有很多用处的。下面给你讲个故事
学计算机的小张同学,从图书馆借了很多关于计算机方面的书籍,想要回去好好学习一下。可就要抱着书本出图书馆门的时候,门禁滴滴滴的响了。图书馆的阿姨赶忙过来询问小张,小张也很懵逼呀。明明所有的书都是刷过的啊。这时阿姨说,可能借书的机器不好用了,找出书来在去借一下就好了。这时候小张开始漫长的找书,一本一本的从门禁中穿过。看的图书馆阿姨都累了。这时阿姨拿过所有的书,把书分成平均两份,从门禁穿过,有一份响了。然后再将响的那一份,分成两份。就这样,阿姨不用几次就找到了没刷成功的书了。看的小张都惊呆了。阿姨对小张说:“这是二分法查找,你刚刚一本一本的找时间复杂度太高了都O(n)了,用二分查找时间复杂度可是O(logn)呢”。小张吃惊,心想图书馆工作人员也是深藏不漏啊
1.二分查找代码实现
其实,二分查找的代码还是比较简单的,不管是递归的还是非递归的。
//非递归二分查找
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;
}
//递归的二分查找
public int bsearch(int []a, int n, int value){
return bsearchInternally(a,0,n-1, value);
}
private int bsearchInternally(int []a,int low ,int high,int value){
if(low > high) return -1;
mid = low + ((high - low) >> 1);
if(value == a[mid]){
return mid;
}else if(value > a[mid]){
return bsearchInternally(a,mid+1,high,value);
}else{
return return bsearchInternally(a,low,mid-1,value);
}
}
这里有三点说一下
-
注意在非递归遍历的时候循环条件是low <= high 是有等号的;
-
在求mid的时候为了防止low+high可能会发生溢出,可换用这种写法 mid = low + (high - low)/2;若追求更加极致的速
度的话,可将除法改为位运算就是递归算法中mid的求法mid = low + ((high - low) >> 1);
-
在这就是mid的更新,注意别搞混了。
2.二分查找的局限性
1.二分查找依赖的是顺序表结构,简单点说就是数组。
2.二分查找针对的是有序的数组。
3.二分查找不适合数据量太小和太大的情况。
3.二分查找的应用
- 查找第一个值等于给定值的元素
//查找第一个值等于给定值的元素
public int bsearch(int []a,int n,int value){
int low = 0;
int high = n - 1;
while(low <= high){
int mid = low + (high - low)/2;
if(value > a[mid]){
low = mid + 1;
}else if(value < a[mid]){
high = mid -1;
}else{
if(mid == 0 || (a[mid -1] != value)) return mid;
else high = mid - 1;
}
}
return -1;
}
其实代码还是比较好理解的,在查找的过程中是是以基本的额二分查找为基础的,就是在判断相等时,以前是直接相等即可返回,现在则是mid==0,显然已经是第一个元素了或者是a[mid - 1] !=value则就是定值的第一个元素了
- 查找最后一个值等于给定值的元素
显然有了第一个的讲解这个基本是比较容易的,直接上代码。
//查找最后一个值等于给定值的元素
public int bsearch(int []a,int n,int value){
int low = 0;
int high = n - 1;
while(low <= high){
int mid = low + (high - low)/2;
if(value > a[mid]){
low = mid + 1;
}else if(value < a[mid]){
high = mid -1;
}else{
if(mid == n-1 || (a[mid +1] != value)) return mid;
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){
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;
}
这个代码在整体思路是上还是和上一个基本是一致的,一个大于等于的元素,则就是前面的都比value值小。这样作为返回的条件极为正确。
-
查找最后一个小于等于给定值的元素
//查找最后一个小于等于给定值的元素 public int bsearch(int []a, int n,int value){ itn low = 0; int high = n - 1; while(low <= high){ 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; }