文章目录
- 概念
- 应用场景
- 代码模板
- OJ练习
- 寻找指定元素1
- 题目描述
- 输入描述
- 输出描述
- 样例
- 题解
- 寻找指定元素2
- 题目描述
- 输入描述
- 输出描述
- 样例
- 题解
- 寻找指定元素3
- 题目描述
- 输入描述
- 输出描述
- 样例
- 题解
- 寻找指定元素4
- 题目描述
- 输入描述
- 输出描述
- 样例
- 题解
- 寻找指定元素5
- 题目描述
- 输入描述
- 输出描述
- 样例
- 题解
- 寻找指定元素6
- 题目描述
- 输入描述
- 输出描述
- 样例
- 题解
概念
二分查找(Binary Search)算法,也叫折半查找算法。二分查找的思想非常简单,有点类似分治的思想。二分查找针对的是一个有序的数据集合,每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0。
为了方便理解,我们以数组
1,3,5,7,11,13,17,19,23,29,31,37,41,43,53,59,在数组中查找37为例,制作了一张查找过程动图,其中low标示左下标,high标示右下标,mid标示中间值下标
可以看出二分查找在查找数字 37 时只需3次,而顺序查找 查找37时需要11次。
应用场景
二分搜索用于在一个单调或者局部单调有序数组中查找一个符合某些条件的值,时间复杂度为O(logN)
代码模板
int BinarySearch(int *a,int len,int key){
int low = 0,high = len-1,mid;
while(low<=high){
mid = (low+high)/2; //取中间位置
if(a[mid] == key){
return mid; //查找成功并返回所在位置
}
else if( key < a[mid] ){
high = mid - 1; //从前半部分查找
}
else{
low = mid + 1; //从后半部分查找
}
}
}
OJ练习
寻找指定元素1
题目描述
在一个严格递增序列A中寻找一个指定元素x,如果能找到,那么输出它的下标;如果不能找到,那么输出。
注:使用二分法实现。
输入描述
输出描述
如果能找到x,那么输出它的下标;否则输出-1。
样例
样例1
输入
5 3
1 2 3 5 8
输出
2
样例2
输入
5 6
1 2 3 5 8
输出
-1
题解
这题就是直接套模板
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6;
int A[N];
int BinarySearch(int *a,int len,int key){
int low = 0,high = len-1,mid;
while(low<=high){
mid = (low+high)/2; //取中间位置
if(a[mid] == key){
return mid; //查找成功并返回所在位置
}
else if( key < a[mid] ){
high = mid - 1; //从前半部分查找
}
else{
low = mid + 1; //从后半部分查找
}
}
return -1;
}
int main(){
int n,x;
cin >> n >> x;
for(int i = 0;i < n;i ++){
cin >> A[i];
}
cout << BinarySearch(A,n,x);
return 0;
}
寻找指定元素2
题目描述
在一个严格递减序列A中寻找一个指定元素x,如果能找到,那么输出它的下标;如果不能找到,那么输出-1。
注:使用二分法实现。
输入描述
输出描述
如果能找到x,那么输出它的下标;否则输出-1。
样例
样例1
输入
5 3
8 5 3 2 1
输出
2
样例2
输入
5 6
8 5 3 2 1
输出
-1
题解
这个就是在板子基础上,把大于号小于反过来就行了,递增变递减
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6;
int A[N];
int BinarySearch(int *a,int len,int key){
int low = 0,high = len-1,mid;
while(low<=high){
mid = (low+high)/2; //取中间位置
if(a[mid] == key){
return mid; //查找成功并返回所在位置
}
else if( key > a[mid] ){
high = mid - 1; //从前半部分查找
}
else{
low = mid + 1; //从后半部分查找
}
}
return -1;
}
int main(){
int n,x;
cin >> n >> x;
for(int i = 0;i < n;i ++){
cin >> A[i];
}
cout << BinarySearch(A,n,x);
return 0;
}
寻找指定元素3
题目描述
在一个递增序列A(可能存在重复元素)中寻找第一个大于等于x的序列下标。
注:使用二分法实现。
输入描述
输出描述
输出第一个大于等于x的序列下标。
样例
样例1
输入
5 5
1 4 5 5 7
输出
2
解释
序列中存在5,所以第一个大于等于5的元素就是5,对应的序列下标是2
样例2
输入
5 6
1 4 5 5 7
输出
4
解释
序列中不存在6,第一个大于等于6的元素是7,对应的序列下标是4
样例3
输入
5 8
1 4 5 5 7
输出
5
解释
序列中所有元素都小于8,因此第一个大于等于8的序列下标是5
题解
循环条件为low<high而非之前的low<=high
,这是由问题本身决定的。在上一个问题中,需要当元素不存在时返回-1,这样当low>high时[low,high]这个区间就不存在了,可以此作为元素不存在的判定原则,因此low<=high满足时循环应当一致执行;但是如果想要返回第一个大于等于x的元素的位置,就不需要判断x本身是否存在,因为就算它不存在,返回的也是“假设它存在,它应该在的位置”,于是当low==high时,[low,high]刚好能夹出唯一的位置,就是需要的结果,因此只需要当low<high时让循环一直执行即可。
由于当low==high时while循环停止,因此最后的返回值既可以是low,也可以是high。
二分的初始区间应当能覆盖到所有可能返回的结果。首先,二分下界是0是显然的,但是二分上界是n-1还是n呢?考虑到要查询元素有可能比序列中的所有元素都要大,此时应当返回n(即假设它存在,它应该在的位置),因此二分上界是n,故二分的初始区间为[low,high]=[0,n]
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6;
int A[N];
int BinarySearch(int *a,int len,int key){
int low = 0,high = len;
while(low<high){
int mid = (low+high)/2;
if(a[mid] >= key){
high = mid;
}
else{
low = mid + 1;
}
}
return low;
}
int main(){
int n,x;
cin >> n >> x;
for(int i = 0;i < n;i ++){
cin >> A[i];
}
cout << BinarySearch(A,n,x);
return 0;
}
寻找指定元素4
题目描述
在一个递增序列A(可能存在重复元素)中寻找第一个大于x的序列下标。
注:使用二分法实现。
输入描述
输出描述
输出第一个大于x的序列下标。
样例
样例1
输入
5 5
1 4 5 5 7
输出
4
解释
序列中存在5,所以第一个大于等于5的元素就是5,对应的序列下标是2
样例2
输入
5 6
1 4 5 5 7
输出
4
解释
序列中不存在6,第一个大于等于6的元素是7,对应的序列下标是4
样例3
输入
5 8
1 4 5 5 7
输出
5
解释
序列中所有元素都小于8,因此第一个大于等于8的序列下标是5
题解
此题和上题类似,只需要把等于号去掉
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6;
int A[N];
int BinarySearch(int *a,int len,int key){
int low = 0,high = len;
while(low<high){
int mid = (low+high)/2;
if(a[mid] > key){
high = mid;
}
else{
low = mid + 1;
}
}
return low;
}
int main(){
int n,x;
cin >> n >> x;
for(int i = 0;i < n;i ++){
cin >> A[i];
}
cout << BinarySearch(A,n,x);
return 0;
}
寻找指定元素5
题目描述
在一个递增序列
A(可能存在重复元素)中寻找一个指定元素x,如果能找到,那么输出第一个x的下标;如果不能找到,那么输出-1。
注:使用二分法实现。
输入描述
输出描述
如果能找到x,那么输出第一个x的下标;否则输出-1。
样例
样例1
输入
5 5
1 5 5 5 7
输出
1
解释
序列中存在三个5,第一个5的序列下标是1
样例2
输入
5 6
1 5 5 5 7
输出
-1
解释
序列中不存在6,因此输出-1
题解
因为会有很多个重复数据,这里只要出现的第一个,那么我们可以改变二分时候查找到key的条件,我们现在不让他查找到就结束,如果查找到了,就让他往左缩短,因为要找的是第一个,如果找最后一个就往右缩短就行了。
需要注意的是,如果没有查找到key,我们需要额外一个bool类型变量,用来判断是否找到过,找到过那个位置就是high+1.没找到就返回-1,可以用三目运算符实现。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6;
bool isFound = false;
int A[N];
int BinarySearch(int *a,int len,int key){
int low = 0,high = len-1;
while(low<=high){
int mid = (low+high)/2;
if(a[mid] == key){
high = mid-1;
isFound = true;
}
else if( key < a[mid] ){
high = mid - 1;
}
else{
low = mid + 1;
}
}
return isFound==true?high+1:-1;
}
int main(){
int n,x;
cin >> n >> x;
for(int i = 0;i < n;i ++){
cin >> A[i];
}
cout << BinarySearch(A,n,x);
return 0;
}
寻找指定元素6
题目描述
在一个递增序列A(可能存在重复元素)中寻找一个指定元素x,如果能找到,那么输出最后一个x的下标;如果不能找到,那么输出-1。
注:使用二分法实现。
输入描述
输出描述
如果能找到x,那么输出最后一个x的下标;否则输出-1。
样例
样例1
输入
5 5
1 5 5 5 7
输出
3
解释
序列中存在三个5,最后一个5的序列下标是3
样例2
输入
5 6
1 5 5 5 7
输出
-1
解释
序列中不存在6,因此输出-1
题解
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6;
bool isFound = false;
int A[N];
int BinarySearch(int *a,int len,int key){
int low = 0,high = len-1;
while(low<=high){
int mid = (low+high)/2;
if(a[mid] == key){
low = mid+1;
isFound = true;
}
else if( key < a[mid] ){
high = mid - 1;
}
else{
low = mid + 1;
}
}
return isFound==true?low-1:-1;
}
int main(){
int n,x;
cin >> n >> x;
for(int i = 0;i < n;i ++){
cin >> A[i];
}
cout << BinarySearch(A,n,x);
return 0;
}