今天分享一个非常简单的算法:二分查找法
一、什么是二分查找法
众所周知二分查找法要比暴力搜索好的多,他们都属于搜索系列的某些搜索的算法。类型 | 查找次数 | 时间复杂度 |
---|---|---|
二分查找法 | 一般不超过100次 | O(n) |
暴力搜索 | 根据数据大小而定,在大量数据中平均次数位n/2次 | O(log2(n)) |
二、实现过程
1.原理解释
二分查找法也可称作为折半查找,对于大量有序数据查找起来十分方便。
最简单的解释方式就是小时候的猜数字游戏。
类比于猜数字游戏二分查找法也是同样的原理
猜数字游戏:
- 首先获得一个数字20
- 在0-100之间猜
- 如果猜的值大于获得的数字,那么就会往后猜
- 如果猜的值小于获得的数字,那么就会往前猜
- 如此往复,直到猜到该数字
*那么我们很容易获得一个公式:(lo+li)/2
猜的次数 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
当前猜的值: | 50 | 25 | 12 | 18 | 21 | 19 | 20 |
获得方式(li+lo)/2: | (100+0)/2 | (50+0)/2 | (25+0)/2 | (25+12)/2 | (25+18)/2 | (21+18)/2 | (21+19)/2 |
判断值: | 大于20 | 大于20 | 小于20 | 小于20 | 大于20 | 小于20 | 等于20(结束) |
可见这种方法要比纯粹的从1猜到20要好一些,特别是面对大量数据时,暴力搜索的时间复杂度为O(n)而二分查找法时间复杂度为O(log2(n))
2.代码实现(二分查找法为猜数字游戏的代码实现)
1、递归实现
/*
* 二分查找法
* 输入:blSearch( (int类型)查找的n值 , (int数组)查找的数组 ,
* (int类型)数组起始点 , (int类型)数组长度-1))
* 输出:将要查找的值n在数组list中的下标
*/
public int biSearch(int n,int[] list,int lo,int li){
//lo和li的初始值分别为0和list.lenght
if(lo > li){
//如果没有这个值输出-1
return -1;
}
//mid为中间值,每次的中间值根据li和lo的位置判断
//也可写作为:
//int mid = (li + lo) / 2;
int mid=(li - lo) / 2 + lo;
if(list[mid] > n){
//如果搜索的值小于目前所获得的数组中的值
//那么上界缩小范围,mid-1是因为要搜索的值不包括list[mid](加不加都可)
//也可直接写为:
//return biSearch(n,list,lo,mid);
return biSearch(n,list,lo,mid-1);
}
//此段和上节相同
else if(list[mid] < n){
return biSearch(n,list,mid+1,li);
}
else{
return mid;
}
}
/*
* 二分查找法入口
* 输入:biSearch( (int类型)查找的n值 , (int数组)查找的数组 )
* 输出:将要查找的值n在数组list中的下标
*/
public int biSearch(int n,int[] list){
return biSearch(n,list,0,list.length);
}
2、循环实现
/*
* 二分查找法
* 输入:blSearch( (int类型)查找的n值 , (int数组)查找的数组 ,
* (int类型)数组起始点 , (int类型)数组长度-1))
* 输出:将要查找的值n在数组list中的下标
*/
public int biSearch(int n,int[] list,int lo,int li){
//创建这个mid
//和上面的思想基本相同
int mid;
while(lo < li){
mid = (lo + li) / 2;
if(n > list[mid]){
lo = mid;
}
else if(n < list[mid]){
li = mid;
}
else{
return mid;
}
}
return 0;
}
/*
* 二分查找法入口
* 输入:biSearch( (int类型)查找的n值 , (int数组)查找的数组 )
* 输出:将要查找的值n在数组list中的下标
*/
public int biSearch(int n,int[] list){
return biSearch(n,list,0,list.length);
}
三、总结:
在网上关于二分查找法有很多的代码与说法,实现起来也各不相同,总结出来大概分为两类:
1:使用公式(li + lo) / 2形式
mid = (lo + li) / 2;
if(n > list[mid]){
lo = mid;
}
else if(n < list[mid]){
li = mid;
}
else{
return mid;
}
2:使用公式(li - lo) / 2 + lo形式
int mid = (li-lo) / 2 + lo;
if(list[mid] < n){
lo = mid + 1;
}
else if(list[mid] > n){
li = mid - 1;
}
else{
return mid;
}
有些同学一看,这有什么不同?
确实,这两种给在本质上没有不同,都是对的,但他们的表达形式不同
他们的不同之处如下:
形式 | 考虑值 |
---|---|
(li + lo ) / 2 | 考虑中间值 :lo = mid或li = mid |
(li - lo) / 2 + lo | 不考虑中间值:lo = mid + 1或li = mid - 1 |
可见第二种确实可能要比第一种有的时候少进行判断一次,因为
第二种没有判断mid,但第一种更容易理解
4、备注
关于二分查找法的可行性,因为二分查找法的前提是已经排列好的数组列表,如果使用二分查找法,则必须考虑数组的有序性问题,如果数组有序,那么使用二分查找法是十分高效的,但若无序则需要考虑爆搜和二分查找法的可行性问题了
下面是我对于二分查找法的数据整理
查找类型 | 暴力搜索 | 二分查找法 |
---|---|---|
查找数据量 | 100 | 100 |
查找时间(包含排序时间) | 0ms | 0ms |
查找类型 | 暴力搜索 | 二分查找法 |
---|---|---|
查找数据量 | 100000 | 100000 |
查找时间(包含排序时间) | 9ms | 109ms |
!!在100000位时居然是二分查找法所花费的时间更多,其原因是快速排序浪费了大量的时间,排序的效率要远远低于爆搜的效率
*可见,虽然二分查找法的效率很高,但其效率高是建立在数组有序的条件之下的,而数组排序要比搜索的时间长很多
*反之,虽然暴力搜索的效率很慢,但是他的搜索稳定,而且不需要排序,是暴力搜索的优势
所以,关于使用二分查找法还是使用暴力搜索确实是见仁见智了。
lo:起始指针
lo:末端指针
mid:搜索指针(和要查找的n进行判断)
参考资料:《算法》《java程序设计和数据结构》