对于升序序列,有常见这样几种二分问题(假设要查的数是v):
- 第一个 >= v的位置
- 第一个 > v的位置
- 最后一个 <=v 的位置
- 最后一个 <v 的位置
个人认为:
前两个问题,是从前往后找,而后两个问题是从后往前找。
所以前两个在二分的时候中间值 m 的取法是向下取整,而后两个是上取整。
前两个左闭右开,后两个左开右闭。
#include <cstdio>
const int N = 10;
int A[N+1] = { 1,2,2,2,4,8,12,13,13,16 };
// [L,R)下的普通二分查找 v
int bsearch( int *A, int L, int R, int v ) {
int mid;
while( L<R ) {
mid = (R+L)/2;
if( A[mid]==v ) return mid;
else if( v<A[mid] ) R = mid;
else L = mid+1;
}
return -1;
}
// ********** [L,R)左闭右开 ***********//
// 1. 第一个 >=v
// [L,R)下 v 的区间下界: 当 v 存在时返回它出现的第一个位置的下标。
// 如果不存在,返回这样一个下标:在此处插入 v (原来的元素后移)后序列仍然有序
int Lower_bound( int *A, int L, int R, int v ) {
while( L<R ){
int m = (L+R)/2; // 向下取整
if( v<=A[m] ) R = m;
else L = m+1;
}
return L;
}
// 2. 第一个 >v
// [L,R)下 v 的区间上界 : 当 v 存在时返回它出现的最后一个位置的后一个位置下标。
// 如果不存在,返回这样一个下标:在此处插入 v (原来的元素后移)后序列仍然有序
int Upper_bound( int *A, int L, int R, int v ) {
while( L<R ){
int m = (L+R)/2;
if( v<A[m] ) R = m;
else L = m+1;
}
return L;
}
// ******** (L,R] 左开右闭 ******** //
// 1. 最后一个 <= v
int Last_less_equal( int *A, int L, int R, int v ){
while( L<R ){
int m = (L+R+1)/2; // 向上取整
if( v>=A[m] ) L = m;
else R = m-1;
}
return L;
}
// 2. 最后一个 < v
int Last_less( int *A, int L, int R, int v ){
while( L<R ){
int m = (L+R+1)/2; // 向上取整
if( v>A[m] ) L = m;
else R = m-1;
}
return L;
}
int main()
{
int v;
while( scanf("%d",&v)==1 ) {
int pos = bsearch( A,0,N,v );
printf("%d is located at %d\n",v,pos);
printf("%d's Lower bound is %d\n",v,Lower_bound(A,0,N,v));
printf("%d's Upper bound is %d\n",v,Upper_bound(A,0,N,v));
printf("last <=%d located %d\n",v,Last_less_equal(A,-1,N-1,v));
printf("last <%d located %d\n",v,Last_less(A,-1,N-1,v));
}
}