一、需满足的两个条件
1.必须是数组(顺序表)
2.必须有序
二、过程演示
如有以下数组(有序数组,并且是从小到大排序):
1.找27 (能找到的情况)
①left,right分别指向数组的两端(用于确定数组的范围)
②用mid=(left+right)/ 2 确定mid的位置,注意(left+right)/ 2 是向下取整!!!
③用mid指向的数与要查找的27做对比,发现36>27,所以查找范围缩小到左边(right移到mid左边)
④重复操作②
⑤对比20与27,发现20<27,所以查找范围缩小到右边(left移到mid右边)
⑥重复操作②,mid =(4+5)/ 2 向下取整为4,此时mid指向4位置,发现27=27,所以找到了!
2.找68(找不到的情况)
①left,right分别指向数组的两端(用于确定数组的范围)
②用mid=(left+right)/ 2 确定mid的位置,注意(left+right)/ 2 是向下取整!!!
③用mid指向的数与要查找的68做对比,发现68>36,所以查找范围缩小到右边(left移到mid右边)
④重复操作②
⑤对比60与68,发现60<68,所以查找范围缩小到右边(left移到mid右边)
⑥重复操作②,mid =(10+11)/ 2 向下取整为10,此时mid指向10位置,发现67<68,所以left移到mid右侧
⑦重复操作②,mid=(11+11)/ 2=11
⑧对比68和71发现,68<71,所以right移到mid左边
⑨此时left在右边,right却在左边了,即left>right,意味着没有查找范围了,意味着没有查找范围了
三、代码编写(为什么容易写错,怎样避免写错)
为什么容易出错呢?我们可以通过下面的例子来加以理解
可以看到,仅在一些细节上有差异的查找内容,查找出来的结果却是大不相同的!
查找的答案:
为什么可以这么处理,我们一起来看下面。
1.一个新的角度
从一个新的角度看二分查找,因为在进行二分查找的数组中,是有序的(从小到大或者从大到小),假设我们要找到第一个>=5的元素,我们就需要界定这个数组的红蓝边界,即K-1和K之间,K在这里是未知的。由此可以知道蓝色区域均为<5的元素,红色区域均为>=5的元素
总结:这个问题的最终目标是把蓝红边界找出来,即求出未知数K。
2.朴素算法
做法1:假设一开始整个数组(数组是从小到大排序的)都是没有颜色的,设计一个蓝色指针在数组最左侧,然后不断移动此指针直至其到达蓝红边界。
做法2:假设一开始整个数组(数组是从小到大排序的)都是没有颜色的,设计一个红色指针在数组最右侧,然后不断移动此指针直至其到达蓝红边界。
这样即可求得蓝红边界。
但是此朴素算法的时间复杂度是O(n),算法效率很低。之所以效率低下,是因为其每次只能拓展一个元素。
那么,我们是不是可以用加速拓展的过程来提高算法效率呢?
答案是当然可以!那就需要用到我们的二分查找了!
3.二分查找算法
①数组初始状态(全无颜色)
②找到无颜色区域中间的那个元素,如图所示。
③发现这个中间元素是属于蓝色区域范畴的(属于蓝色区域有条件,比如小于5),于是直接拓展蓝色区域到这个元素的位置!
④继续这样的操作,找到剩下无颜色区域的中间元素
⑤发现这个中间元素是属于红色区域范畴的(属于红色区域也有条件,比如大于等于5),于是直接拓展红色区域到这个元素的位置!
⑥继续上面相同的操作,直至整个数组都有颜色时,就可以找到蓝红边界了!
二分查找的时间复杂度为O(logn),这是因为我们每次循环中都将无颜色区域的长度缩小一半,因此我们大概需要logn次便可以将无颜色区域的长度缩小为0。(logn一般以2为底来计算)
4.二分查找伪代码
假设数组是从下标0开始到N-1结束(即数组有N个元素)
细节一:为什么不能初始化l=0,r=N-1
此时如果问题是找到小于等于4的第一个数,那么此时整个数组均为红色区域(>4),此时会产生bug,因为蓝色区域实际上在数组外边。
细节二:m始终处于[0,N)以内,即m不会溢出
细节三:更新指针时,能不能写成l=m+1,或者r=m-1
这样会容易出错!
细节四:while l+1不等于r,不会陷入死循环
例题:数的范围
题目概述:
给定一个按照升序排列的长度为n的整数数组,以及q个查询。
对于每个查询,返回一个元素k的起始位置和终止位置(位置从0开始计数)
如果数组中不存在该元素,则返回-1 -1。
输入格式:
第一行包含整数n和q,表示数组长度和询问个数。
第二行包含 n个整数(均在1~ 10000 范围内),表示完整数组。
接下来q行,每行包含一个整数k,表示一个询问元素。
输出格式:
共q行,每行包含两个整数,表示所求元素的起始位置和终止位置,如果数组中不存在该元素,则返回-1 -1。
数据范围:
1 ≤n≤ 100000
1 ≤q≤ 10000
1 ≤k≤ 10000
代码编写:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100010;
int n,q;
int arr[N];
//数组中的数是num,查找的数是x
bool isBlue1(int num,int x){
if(num<x)return true;
else return false;
}
//第一个二分查找找的是:被询问数的第一次出现的位置(下标)
int binary_search1(int arr[],int len,int x){
int l=-1,r=len;
while(l+1<r){ //l+1!=r是l+1<r的子集
int mid=(l+r)/2;
if(isBlue1(arr[mid],x)){
l=mid;
}
else r=mid;
}
if(arr[r]==x)return r;
else return -1; //找不到就返回-1
}
//数组中的数是num,查找的数是x
bool isBlue2(int num,int x){
if(num<=x)return true;
else return false;
}
//第二个二分查找找的是:被询问数的次出现的位置(下标)
int binary_search2(int arr[],int len,int x){
int l=-1,r=len;
while(l+1<r){
int mid=(l+r)/2;
if(isBlue2(arr[mid],x)){
l=mid;
}
else r=mid;
}
if(arr[l]==x)return l;
else return -1; //找不到就返回-1
}
int main(){
scanf("%d %d",&n,&q);
for(int i=0;i<n;i++){
scanf("%d",&arr[i]);
}
while(q--){
int x;
scanf("%d",&x);
int res1=binary_search1(arr,n,x);//第一次查找x的起始位置
int res2=binary_search2(arr,n,x);//第二次查找x的终止位置
printf("%d %d",res1,res2);
}
return 0;
}
运行样例:
洛谷:P2249 【深基13.例1】查找
题目描述
输入 n 个不超过 109 的单调不减的(就是后面的数字不小于前面的数字)非负整数 a1,a2,…,an,然后进行 m 次询问。对于每次询问,给出一个整数 q,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 −1 。
输入格式
第一行 2 个整数 n 和 m,表示数字个数和询问次数。
第二行 n 个整数,表示这些待查询的数字。
第三行 m 个整数,表示询问这些数字的编号,从 1 开始编号。
输出格式
输出一行,m 个整数,以空格隔开,表示答案。
输入输出样例
输入
11 3 1 3 3 3 5 7 9 11 13 15 15 1 3 6
输出
1 2 -1
说明/提示
数据保证,1≤n≤106,0≤ai,q≤109,1≤m≤105
本题输入输出量较大,请使用较快的 IO 方式。
解题代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1000010;
bool isBlue(int num,int x){
if(num<x){
return true;
}
else return false;
}
int binary_search(int q[],int len,int x){
int l=0,r=len+1;
while(l+1<r){
int m=(l+r)/2;
if(isBlue(q[m],x)){
l=m;
}
else r=m;
}
if(q[r]==x){
return r;
}
else return -1;
}
int n,m;
int q[N];
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&q[i]);
}
while(m--){
int x;
scanf("%d",&x);
int res=binary_search(q,n,x);
printf("%d ",res);
}
return 0;
}
关键思路
用二分查找的思路
洛谷:P1102 A-B 数对
题目背景
出题是一件痛苦的事情!
相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!
题目描述
给出一串正整数数列以及一个正整数 C,要求计算出所有满足 A−B=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。
输入格式
输入共两行。
第一行,两个正整数 N,C。
第二行,N 个正整数,作为要求处理的那串数。
输出格式
一行,表示该串正整数中包含的满足 A−B=C 的数对的个数。
输入输出样例
输入
4 1 1 1 2 3
输出
3
说明/提示
对于 75% 的数据,1≤N≤2000。
对于 100% 的数据,1≤N≤2×105,0≤ai<230,1≤C<230。
2017/4/29 新添数据两组
解题代码:
当我尝试用常规的方法进行解题时,写出以下代码,进入测试后发现还有一些示例不能通过:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=200010;
int n,C;
int q[N];
int main(){
scanf("%d %d",&n,&C);
for(int i=1;i<=n;i++){
scanf("%d",&q[i]);
}
int cnt=0;//计数
for(int a=1;a<=n;a++){
for(int b=1;b<=n;b++){
if(q[a]-q[b]==C){
cnt++;
}
}
}
printf("%d\n",cnt);
return 0;
}
这是为什么呢?我们开始进行分析!
通过上图我们可以知道是“时间超限”的问题:
c++一秒算10的9次方比较吃力,基本10的9次方就跑不出来了,那么最好是把运算数量控制到10的7次方~10的8次方。
分析n:
由题目知道,对于百分之百的数据n的取值最大为2*10^5,而且n经历了嵌套,所以运算数量会有4*10^10那么多,超出了10的9次方!
分析cnt:
int的最大值的2147483647,将它估计为2*10^9,再分析计数cnt最大可能有10^5*10^5那么大,即10^10,因为2*10^9小于10^10,所以用int来定义cnt可能不够,所以改成long long
int的最大值有2^31-1,long long的最大值有2^63-1,约为1*10^19
具体怎么解决呢,肯定要对计算的式子进行优化吧!
换一个思路,我们使用B+C=A进行查找计数,那么我们需要对B进行枚举,在取到B的每一个可能的取值时对其加上C,再查找A,这种情况下A有几个就进行几次cnt++。那么我们开始对这个想法进行算法优化,优化如下:
for(int B=1;B<=n;B++){
int A=B+C
二分查找,找到A。(需要找log以2为底2*10^5,计算得需要大约17次)
}
所以此时查找需要查找2*10^5*17次
下面是引入二分查找的代码(通过率为100%):
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=200010;
int n,C;
int q[N];
int bSearch1(int q[],int len,int x){
int l=0,r=n+1;
while(l+1<r){
int mid=(l+r)/2;
if(q[mid]<x){
l=mid;
}
else r=mid;
}
if(q[r]==x) return r;
else return -1;
}
int bSearch2(int q[],int len,int x){
int l=0,r=n+1;
while(l+1<r){
int mid=(l+r)/2;
if(q[mid]<=x){
l=mid;
}
else r=mid;
}
if(q[l]==x) return l;
else return -1;
}
int main(){
scanf("%d %d",&n,&C);
for(int i=1;i<=n;i++){
scanf("%d",&q[i]);
}
//排序:因为数据从数组下标为1处开始存,若是从0开始就是sort(q,q+n)
sort(q+1,q+1+n);
long long cnt=0;//计数
for(int B=1;B<=n;B++){
int A=q[B]+C;
int res1=bSearch1(q,n,A);
if(res1==-1){
continue;
}
else{
int res2=bSearch2(q,n,A);
cnt += res2-res1+1;
}
}
printf("%lld\n",cnt);//cnt是long long型,注意输出!
return 0;
}
此文章中的图片部分截自网络,供小编和大家共同参考学习。如有错误请指正,谢谢!