一、前言
对于二分查找有很多种实现方式,我们只需要牢记一种即可,下面分享一种代码简单,思维简单并且不会陷入死循环的代码,当然我们也可以用c++的函数来实现。
二、二分的代码实现
二分的前提肯定是已经排好序了,分三种情况来讨论。
- 数据中没有重复的,例如:1 2 3 4 5 ;
- 数据中有重复的,例如: 1 2 2 2 3 4 4 5;
对于这两种情况的查找肯定是不太相同的,前者只要判断有无,而后者一般需要求出数据的第一次出现的位置和最后一次出现的位置。
- 其实还有一种就是实数二分,详见例题二,这种比较简单看完题解即可掌握,不在此描述
1.分区间实现
以下代码就是把数据分为两段,一段是小于(等于)X的,一段是大于(等于)X的。其中l是左区间的右端点,r为右区间的左端点
模板
对于一个有序数组,假设下标为0,1,2,3…,n-1;总共n个数字
int L=-1,R=n; //注意是-1和 n
while(L+1!=R)
{
int mid=L+R>>1; //位运算相当于(L+R)/2
if(check()) L=mid; //这里的check就是你所选取的条件
else R=mid;
//最后根据你所分左右两边区间的结果
//选取L或者R作为结果
}
到这里看不懂没关系,可以先看下面的一个实例
#include<iostream>
using namespace std;
int main()
{
int a[6]={1,2,2,3,3,4};
int l=-1,r=6;//定义两个指针
while(l+1!=r)
{
int mid=l+r>>1;
if(a[mid]<3) l=mid;//或者if(a[mid]>=3) r=mid;
else r=mid; // else l=mid; //这里要输出r
}
cout<<"第一个3所在的下标为 "<<r<<endl;
l=-1; r=6;//定义两个指针
while(l+1!=r)
{
int mid=l+r>>1;
if(a[mid]>3) r=mid;//或者if(a[mid]<=3) l=mid;
else l=mid; // else r=mid;
}
cout<<"最后一个3所在的下标为 "<<l<<endl;
return 0;
}
结果
以求第一个3下标为例,我们的判断条件是if(a[mid] < 3) l=mid;
else r=mid; 所以我们划分了两个区间一个是<3 的另一个是>=3的,LR是区间端点。
这里的LR其实也不用特别去记哪个再if下哪个在else下,因为mid现在的位置值<3,所以肯定只能是L=mid,R代表的是大于3的区间,其他类推
当L+1==R的时候,while退出,最后L肯定是左边区间的又端点,R肯定是右边区间的左端点
如果还是不太理解的话,强烈建议自己手动实现以下。
要想更深刻的了解为什么的可以看这这篇文章:不需要考虑mid+1、mid-1的二分查找模板
2.直接返回查找值的位置
模板取自严蔚敏编写的《数据结构 c语言版 第2版》
int Search()
{
low=1; high=length //数组的左右位置
while(low<=high)
{
mid=(low+high)/2;
if(key==a[mid]) return mid;
else if(key<a[mid]) high=mid-1;
else low=high+1;
}
return -1;//表示查找失败
}
这个算法容易理解,不再过多解释,但同时也可以发现我们只能返回一个位置,当数据中有多个相同的值的时候(例如:… 7 7 7 7 7 …)无法判断返回的是哪一个,仅限于查找是否存在,并返回一个位置。
三、c++函数实现二分查找
要用二分查找函数必须带上头文件algorithm
lower_bound():返回大于或等于目标值的第一个位置,如果所有元素都小于目标值,则返回last的位置
upper_bound():返回大于目标值的第一个位置,如果目标值大于数组中全部元素,返回的是last
binary_search():若目标值存在则返回true,否则返回false
这三个函数都有三个参数:分别为数组的起始位置、数组的终止位置(开区间取不到)以及要查找的目标值,返回值为物理地址,因此要获得对应的逻辑地址,需要减去数组的起始位置。
举例:
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int a[6]={1,2,2,3,3,4};
cout<<lower_bound(a,a+6,2)-a<<endl;
cout<<upper_bound(a,a+6,2)-a<<endl;
cout<<lower_bound(a,a+6,5)-a<<endl;
cout<<upper_bound(a,a+6,5)-a<<endl;
return 0;
}
输出
总结:
lower_bound()和 upper_bound()就对应着二分的区间查找方式。
binary_search()就是上文第二种方式
四、例题
1.数的范围
原题链接:Acwing 数的范围
可以分区间实现也可以直接用函数实现。
这里只给出分区间的代码,函数实现较为简单:
#include<iostream>
using namespace std;
const int N=100010;
int a[N];
int n,m,k;
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
while(m--)
{
cin>>k;
int l=-1,r=n;
while(l+1!=r)
{
int mid=l+r>>1;
if(a[mid]<k) l=mid;
else r=mid;
}
if(a[r]==k)
{
cout<<r<<" ";
int l=r,r=n;
while(l+1!=r)
{
int mid=l+r>>1;
if(a[mid]>k) r=mid;
else l=mid;
}
cout<<l<<endl;
}
else cout<<"-1 -1"<<endl;
}
return 0;
}
2.数的三次方根
原题链接:Acwing 数的三次方根
代码实现
#include<iostream>
using namespace std;
int main()
{
double n;
cin>>n;
double l=-10000;
double r=10000;
while(r-l > 1e-7) //1e-7是10的-7次方,所以保留6位是-7而不是-6
{
double mid=(l+r)/2;
if(mid * mid * mid >=n) r=mid;
else l=mid;
}
printf("%.6lf\n",l);
return 0;
}