【二分查找】
问题:如何在一个严格递增序列 中找出给定的数 。
//A[]为严格递增序列,left为二分下界,right为二分上界,x为欲查询的数
//二分区间为左闭右闭的[left,right],传入的初值为[0,n-1]
int binarySearch(int A[], int left, int right, int x)
{
int mid; //mid为left和right的中点
while(left <= right) //如果left>right就没办法形成闭区间了
{
mid = (left + right) / 2; //取中点
if(A[mid]==x)
return mid; //找到x,返回下标
else if(A[mid] > x) //中间的数大于x
right = mid - 1; //往左子区间[left,mid-1]查找
else //中间的数小于x
left = mid + 1; //往右子区间[mid+1,right]查找
}
return -1; //查找失败,返回-1
}
注意中间值mid:
中间值写成 或者 都行。不过, 如果 很大, 可能溢出, 用前一种更好。不能写成 ,在有负数的情况下,会出错。
【求序列中第一个大于等于x的元素的位置】
问题:如果递增序列 中的元素可能重复,那么如何对给定的欲查询元素 ,求出序列中第一个大于等于 的元素的位置 以及第一个大于 的元素的位置 ,这样元素 在序列中的存在区间就是左闭右开区间 。
//A[]为递增序列,x为欲查询的数,函数返回第一个大于等于x的元素的位置
//二分上下界为左闭右闭的[left, right],传入的初值为[0,n]
int lower_bound(int A[], int left, int right, int x)
{
int mid; //mid为left和right的中点
while(left<right) //对[left,right]来说,left==right意味着找到唯一位置
{
mid = left+(right-left)/2; //取中点
if(A[mid] >= x) //中间的数大于等于x
right = mid; //往左子区间[left,mid]查找
else //中间的数小于x
left = mid + 1; //往右子区间[mid+1,right]查找
}
return left; //返回夹出来的位置
}
注意:
- 循环条件为 , 而非之前的 ,这是由问题本身决定的。
- 由于当 时while循环停止,因此最后的返回值既可以是 ,也可以是 。
- 二分的初始区间应当能覆盖到所有可能返回的结果,故二分的初始区间为
【求序列中第一个大于x的元素的位置】
//A[]为递增序列,x为欲查询的数,函数返回第一个大于x的元素的位置
//二分上下界为左闭右闭的[left,right],传入的初值为[0,n]
int upper_bound(int A[], int left, int right, int x)
{
int mid; //mid为left和right的中点
while(left < right) //对[left,right]来说,left==right意味着找到唯一位置
{
mid = left+(right-left)/2; //取中点
if(A[mid] > x) //中间的数大于x
right = mid; //往左子区间[left,mid]查找
else //中间的数小于x
left = mid + 1; //往右子区间[mid+1,right]查找
}
return left; //返回夹出来的位置
}
和 函数的代码相比, 函数只是把代码中的 改成了 ,其他完全相同。
【模板】
解决 “寻找有序序列第一个满足某条件的元素的位置” 问题的固定模板。
1、二分区间为左闭右闭的 ,初值必须能覆盖解的所有可能取值。
所谓的“某条件”在序列中一定是从左到右先不满足,然后满足的(否则把该条件取反即可)。
int solve(int left, int right)
{
int mid; //mid为left和right的中点
while(left<right) //对[left,right]来说,left==right意味着找到唯一位置
{
mid = left+(right-left)/2; //取中点
if(条件成立) //条件成立,第一个满足条件的元素的位置<=mid
right = mid; //往左子区间[left,mid]查找
else //条件不成立,则第一个满足该条件的元素的位置>mid
left = mid + 1; //往右子区间[mid+1,right]查找
}
return left; //返回夹出来的位置
}
如果想要寻找最后一个满足“条件C”的元素的位置,则可以先求第一个满足“条件!C”的元素的位置,然后将该位置减 即可。
2、使用左开右闭的写法也可以,并且与左闭右闭的写法等价。
//二分区间为左开右闭的(left,right]
//初值必须能覆盖解的所有可能取值, 并且left比最小取值小1
//例如对下标从0开始的序列来说,left和right的取值应为-1和n
int solve(int left, int right)
{
int mid; //mid为left和right的中点
while(left + 1 < right) //对(left,right]来说,left+1==right意味着找到唯一位置
{
mid = left+(right-left)/2; //取中点
if(条件成立) //条件成立,第一个满足条件的元素的位置<=mid
right = mid; //往左子区间[left,mid]查找
else //条件不成立,则第一个满足该条件的元素的位置>mid
left = mid; //往右子区间[mid,right]查找
} //改为left = mid,并且返回的应当是right而不是left
return right;
}
【STL中的lower_bound()和upper_bound()】
前面扯了那么多,总结起来就是,如果只是简单地找 或 附近的数, 就用 STL 的 和 函数。
和 都是利用二分查找的方法在一个排好序的数组中进行查找的。
在从小到大的排序数组中,
-
:从数组的 位置到 位置二分查找第一个大于或等于 的数字,找到返回该数字的地址,不存在则返回 。通过返回的地址减去起始地址 ,得到找到数字在数组中的下标。
-
:从数组的 位置到 位置二分查找第一个大于 的数字,找到返回该数字的地址,不存在则返回 。通过返回的地址减去起始地址 ,得到找到数字在数组中的下标。
在从大到小的排序数组中,重载 和
-
:从数组的 位置到 位置二分查找第一个小于或等于 的数字,找到返回该数字的地址,不存在则返回 。通过返回的地址减去起始地址 ,得到找到数字在数组中的下标。
-
:从数组的 位置到 位置二分查找第一个小于 的数字,找到返回该数字的地址,不存在则返回 。通过返回的地址减去起始地址 ,得到找到数字在数组中的下标。