二分查找是一种效率非常高的算法。
在头文件< algorithm>中已经有二分查找的实现。
而且binary_search()的使用一般是和STL配套使用。(后面有关于STL无序容器的排序函数书写讲解)
一:STL二分查找
1.binary_search()函数
该函数的功能是查看某一值在已经排好的序列中是否存在,当存在的时候返回true,否则返回false。返回值是一个bool类型,也说明了binary_search()支持更加广泛的数据类型,这些数据结构可能不支持随机访问,所以就算是返回了下标也是没有用的。函数原型为:
//default (1)
template <class ForwardIterator, class T>
bool binary_search (ForwardIterator first, ForwardIterator last,
const T& val);
//custom (2)
template <class ForwardIterator, class T, class Compare>
bool binary_search (ForwardIterator first, ForwardIterator last,
const T& val, Compare comp);
注意序列是迭代器表示,原则上可以带入多种数据类型。参数说明如下:
first:序列需要查询的开始位置。
last:序列需要查询的结束位置。
val:需要查询的元素。
comp:用户自定义的比较函数。
函数原型中给出了两种模板,区别在于是否有用户自己写的比较函数。第一种是按照运算符”<”进行元素之间的比较,对于第二种使用用户自己定义的comp函数进行比较。
binary_search()函数是在序列[first,last)中查找是否存在某一元素(注意区间是左闭右开),最重要的是序列一定是有序的,而且排序的比较函数要和二分查找的比较函数是一样的。
样例如下:
#include <cstdio>
#include <iostream>
#include <vector>
#include <iostream>
#include <map>
#include <algorithm>
using namespace std;
int main()
{
int n;
int value;
scanf("%d %d",&n,&value);
vector<int > a;
while(n--)
{
int temp;
scanf("%d",&temp);
a.push_back(temp);
}
sort(a.begin(),a.end());
vector<int >::iterator it1,it2;
it1 = a.begin();
it2 = a.end();
bool flag;
flag = binary_search(it1,it2,value);
cout<<flag<<endl;
return 0;
}
关于上述代码,有以下几点需要说明:
- sort()函数对于vector的排序函数,参数是两个迭代器,一个指向第一个元素(a.begin())和一个指向最后一个元素的后一个位置(a.end())。
- 迭代器类似与指针地址,访问的时候加上*。
- binary_search()函数的结束迭代器也是指向最后一个元素的后一个位置。(所以我们大致的可以知道这样的一个规律就是结束迭代器一般都是指end())。
- bool类型类似与int类型,所以要是使用printf的话使用%d。
- binary_search()函数的查找值应该是与序列的元素类型一样(在函数原型中定义value采用的引用类型).
2.lower_bound()函数
binary_search()函数只能告诉我们某一个值是否存在,但是无法确定的知道这个元素所在位置。
在头文件< algorithm>中定义了lower_bound()函数,函数原型如下:
//default (1)
template <class ForwardIterator, class T>
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,
const T& val);
//custom (2)
template <class ForwardIterator, class T, class Compare>
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,
const T& val, Compare comp);
这个函数的参数和binary_search()函数是一样的,但是这个函数的返回类型是ForwardIterator,之所以返回一个迭代器是因为STL很多容器都是不支持下标访问的(关联容器)。
这个函数的功能就是返回迭代器的下界。
也就是说,如果要查找的元素在序列中只有一个,那么lower_bound() 函数返回这个元素的地址。
如果在序列中有多个(因为是有序的,所以一定会邻接),那么这个结果序列可以用[first,last)表示(左闭右开),那么 lower_bound()返回的就是first的地址。
3.upper_bound()函数
upper_bound()函数是返回迭代器的上界。函数原型如下:
//default (1)
template <class ForwardIterator, class T>
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,
const T& val);
//custom (2)
template <class ForwardIterator, class T, class Compare>
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,
const T& val, Compare comp);
表示相同元素的区间[first, last)是左闭右开的,这就造成了lower_bound()和upper_bound()的一个不同之处:lower_bound()可以相等,upper_bound()不能相等。
也就说如果元素只出现一次,那么lower_bound()找到了这个元素的地址,但是upper_bound()找到的却是它的下一个;
如果相同元素出现了多次,那么lower_bound() 找到了第一个所找元素的地址,但是upper_bound()找到的却是最后一个元素的下一个元素的地址。
4.非STL代码演示:
#include <iostream>
#include <algorithm>
#define MAXN 10005
using namespace std;
int a[MAXN];
int main()
{
int n;
int value;
cin>>n>>value;
for(int i=0;i<n;i++)
cin>>a[i];
sort(a,a+n);
bool flag;
flag = binary_search(a,a+n,value);
cout<<flag<<endl;
return 0;
}
sort()函数与二分查找函数的默认顺序是一样的,都是按照小于运算符进行的。
二:自己写二分查找算法
在使用的时候多数还是会自己写二分查找函数的,模板总会有受限制的地方。
二分查找算法使用的前提就是数组一定是有序的(自己写二分查找算法的时候对于一些较为复杂的数据类型需要重载运算符,一般是重载小于号(后面会有文章进行说明运算符的重载))。
二分查找的代码如下:
int binary_search(int a[] , int len , int value)
{
int low = 0;//数组是从0开始的
int high = len -1;
while(low <= high)
{
int middle = (high - low) / 2 + low;//直接使用(low + high) / 2可能会溢出
if(a[middle] == value)
return middle;
else if(a[middle] > value)
high = middle -1 ; //因为middle下标已经进行过比较了,所以减一
else
low = middle + 1;
}
return -1;
}
其中当low等于high的时候还会进行一次循环,这时候middle=high=low。
如果一开始的下标是0,1,2,3,4,5,那么low=0,high=5,middle=2,也就说如果有偶数项就是向下取;如果一开始的下标是0,1,2,3,4,那么low=0,high=4,middle=2,也就是说如果有奇数项就是取中间的那一个下标。
关于边界的说明,如果给出的value是等于有序数列的最后一个元素,那么循环过程中的low一直进行加一操作,知道low=high=最后一个元素的下标。
三:各种二分查找
1.给定一个有序(非降序)的数组A,求任意一个i使得A[i]等于target,不存在返回-1 。
注意:时间复杂度logn,返回的值是数组下标。(位置减一)
int Binary_search(int A[],int len,int value)
{
int low = 0;
int high = len-1;
while(low < high)
{
int middle = (high-low)/2 + low;
if(A[middle] == value)
return middle;
else if(A[middle] > value)
high = middle - 1;
else if(A[middle] < value)
low = middle + 1;
}
return -1;
}
2.给定一个有序(非降序)的数组A,可含有重复元素,求最小的i使得A[i]等于target,不存在返回-1。
注意:最常见的思路就是首先找到等于value的任意一个i,然后往左区间遍历,知道找到第一个等于value的i值,但是存在一种最差的情况就是左区间的所有值都是value,那么时间复杂度就是O(n),就不能体现二分的思想了。
int Binary_search_First(int A[],int len,int value)
{
int low = 0;
int high = len - 1;
while(low < high)
{
int middle = (high-low)/2 + low;
if(A[middle] < value)
low = middle + 1;
else if(A[middle] >= value)
high = middle;
}
if(A[low] != value)
return -1;
else
return low;
}
分析:这里A[high]是可以的等于value,因为右边的重复value值对于求解是没有意义的。另外需要注意的就是如果是high找到了是等于middle,因为返回条件并不是A[middle] = value,而是找最小的i,所以等于的时候,不能让high = middle-1,防止跳过要找的value。(感觉就像是high在逼近low)
3.给定一个有序的(非降序)数组A,可含有重复元素,求最大的i使得A[i]等于target,不存在返回-1 。
注意:与上一个问题基本一致,但是可能因为向下取整的原因出现死循环。
int Binary_search_Last(int A[],int len,int value)
{
int low = 0;
int high = len - 1;
while(low < high)
{
int middle = (high-low+1)/2 + low;
//这里进行了加以操作,当low+1=high的时候,middle=low,一直是死循环。
if(A[middle] > value)
high = middle -1;
else if(A[middle] <= value)
low = middle;
}
if(A[high] != value)
return -1;
else
return high;
}
4.给定一个有序的(非降序的)数组A,可含有重复元素,求最大的i使得A[i]小于target,不存在返回-1 。
这里写代码片
5.给定一个有序的(非降序的)数组A,可含有重复元素,求最小的i使得A[i]大于target,不存在返回-1 。
这里写代码片
6.给定一个有序的(非降序的)数组A,可含有重复元素,求target在数组中 出现的次数,不存在返回-1 。
这里写代码片
7.给定一个有序的(非降序的)数组A,可含有重复元素,若target存在,返回第一次出现的位置,如果不存在,返回应该插入的位置。
这里写代码片
8.给定一个有序的(非降序的)数组A,可含有重复元素,求绝对值最小的元素的位置。
这里写代码片
9 .给定一个有序的(非降序的)数组A和一个有序的(非降序的)数组B,可含有重复元素,求两个数组合并以后第K个元素。
这里写代码片
10.