lower/upper bound
有些时候我们需要知道有序数列中第一个大于或等于k(lower bound)的数或者第一个大于k的数(upper bound),如果我们一个一个查找时间复杂度是O(n)的,在STL(Algorithm库)中有两个函数lower_bound,和upper_bound,函数支持以上操作,为了更好地理解以上函数我们将手写上述函数并讲解它的工作原理.
[算法描述]
我们假设我们所需要查找的数组是a[]对于每个函数我们都设置3个变量,第一个是左端点,第二个是右端点,第三个是需要查找的k,边界都是当前查找的区间(left,right)中都有left<right否则不再继续查询.对于lower bound(查找第一个大于或等于k的数)而言,我们每次查找中间的一个数,这个数大于或等于k时我们将right赋值为mid,否则就把left赋值为mid+1(因为此时mid不是解所以我们可以直接从mid+1到right查找下一个解),那么现在有两种情况(1)区间内有解,这样我们找出来的left保存的就是解直接返回left的值即可,(2)区间内无解,此时left=right=r,这时我们规定如果返回r+1即无解所以这时返回left+1即可(其实等价与r+1)(注意由于有可能最后答案为r时可能正好是唯一解,为了解决这个问题我们最后判断一下当前改点是否小于k如果小于k执行上述操作即可)(当然也可以一开始就判断一下最后一个数和第一个数试一下是否有解).upper bound 类似,我们在二分的时候把mid>k时把right赋值为mid(因为我们要找的是第一个大于k数值所以这里并不能等于),其他和lower bound类似.注以上返回的其实是a[]中对应数值所在下标(STL中返回的是迭代器位置).
[代码(lower bound)]
inline int lower_bound(int l,int r,int k){ //返回在数组a中第一个大于等于k的下标 int left=l,right=r,mid=0; while(left<right){ //设置边界 mid=left+(right-left)/2; //取终点 if(a[mid]>=k){ //由于我们要找的是第一个大于等于k的下标 //所以中点如果大于或等于k,我们就在(left,mid)中查找是否有一个更小答案 right=mid; }else{ //如果中点小于k,我们就在(mid+1,right)中查找是否具有答案 left=mid+1; } } //由于我们找的是区间(l,r)之间如果中间不存在解,那么就输出r+1(经过以上操作后left=r) if(a[left]<k){ //这里有且只有在无解的时候才会++ left++; } return left; }
[代码(upper_bound)]
inline int upper_bound(int l,int r,int k){ //返回在数值a中第一个大于k的下标 int left=l,right=r,mid=0; while(left<right){ //设置边界 mid=left+(right-left)/2; //取中点 if(a[mid]>k){ //由于我们要找的是第一个大于k的下标 //所以中点如果大于k就在(left,mid)中寻找 right=mid; }else{ //如果中点小于k,我们就在(mid+1,right)中寻找是否有答案 left=mid+1; } } if(a[left]<=k){ //只有无解时才会++ left++; } return left; }
[代码(调用即操作示范)]
/* Name: Lower/Upper Bound Author: FZSZ-LinHua Date: 2018 06 13 Time complexity: O(log n) Algorithm: Divide-and-conquer */ # include "iostream" # include "cstdio" const int maxm=10000+10; int n,m,a[maxm],k; inline int lower_bound(int l,int r,int k){ //返回在数组a中第一个大于等于k的下标 int left=l,right=r,mid=0; while(left<right){ //设置边界 mid=left+(right-left)/2; //取终点 if(a[mid]>=k){ //由于我们要找的是第一个大于等于k的下标 //所以中点如果大于或等于k,我们就在(left,mid)中查找是否有一个更小答案 right=mid; }else{ //如果中点小于k,我们就在(mid+1,right)中查找是否具有答案 left=mid+1; } } //由于我们找的是区间(l,r)之间如果中间不存在解,那么就输出r+1(经过以上操作后left=r) if(a[left]<k){ //这里有且只有在无解的时候才会++ left++; } return left; } inline int upper_bound(int l,int r,int k){ //返回在数值a中第一个大于k的下标 int left=l,right=r,mid=0; while(left<right){ //设置边界 mid=left+(right-left)/2; //取中点 if(a[mid]>k){ //由于我们要找的是第一个大于k的下标 //所以中点如果大于k就在(left,mid)中寻找 right=mid; }else{ //如果中点小于k,我们就在(mid+1,right)中寻找是否有答案 left=mid+1; } } if(a[left]<=k){ //只有无解时才会++ left++; } return left; } int main(){ scanf("%d%d",&n,&m); //n个数存入a[]中,m次操作 register int i; int x; for(i=1;i<=n;i++){ scanf("%d",&a[i]); } for(i=1;i<=m;i++){ scanf("%d",&x); //需要查询的是x printf("%d\n",lower_bound(1,n,x)); //在整个数组中查找(这里只返回下标无论是否有解) } return 0; }
原文地址:https://www.cnblogs.com/FJ-LinHua/p/9180857.html