题目:设有n个数已经按从小到大的顺序排列,现在输入x,判断它是否在这n个数中。
分析:(想要实现二分查找的前提所输入的数据必须按照从小到大或从大到小预先排列,这里所分析的情况依照题目所说为准)本题是典型的二分查找,那必须要有中点以及区间范围,那不妨设左端点为i,右端点为j,中点为mid。所输入的数据用数组a保存
那么首先想要找到这个数,前提条件就是i<=j。
以下分为2种情况:
1.当a[mid]>x时,说明x在较小的区间内,那么这时候的左端点i不变,右端点j变成mid-1。
2.当a[mid]<x时,说明x在较大的区间内,那么这时候的右端点j不变,左端点i变成mid+1。
Code:(循环)
#include<iostream>
using namespace std;
int main(){
int a[100],i,j,mid,n,x;
cin>>n;
for(i=0;i<n;i++)
cin>>a[i];
cin>>x;
for(i=0,j=n-1,mid=(i+j)/2;i<=j&&a[mid]!=x;mid=(i+j)/2)
if(a[mid]>x) j=mid-1;
else i=mid+1;
if(i<=j) cout<<mid<<endl;
else cout<<"not found"<<endl;
return 0;
Code:(递归)
#include<iostream>
using namespace std;
int search(int a[],int i,int j,int x){
int mid;
mid=(i+j)/2;
if(i>j) return -1;
if(a[mid]==x) return mid;
if(a[mid]>x) return search(a,i,mid-1,x);
else return search(a,mid+1,j,x);
}
int main(){
int a[100],n,i,x,tmp;
cin>>n;
for(i=0;i<n;i++)
cin>>a[i];
cin>>x;
tmp=search(a,0,n-1,x);
if(tmp<0) cout<<"not found"<<endl;
else cout<<tmp<<endl;
return 0;
}
二分写法种类
二分写法的种类很多,最常见的就是二分查找了的最普遍写法了。代码如下:
bool bFind(int a[],int left,int right,int tag)
{
for( ;left<=right;)
{
int mid = (left + right)>>1;
if(a[mid]==tag)
return tag;
else
a[mid]<tag ? l=mid + 1 : r = mid -1;
}
return false;
}
正如我刚才说的,这只是最普通的二分,实际上,二分还可以实现非等值查找,例如平时所说的二分求上界,二分求下界之类的。
下面我用乘法原理来告诉你们二分查找有多少种写法。
区间的开闭 - [开区间还是闭区间]
左右端点 - [这个端点是和上面区间开闭联系的,具体表现为左开还是左闭,右开还是右闭]
中点是取整还是进一 - [在计算中点的时候到底是(left + right) >> 1还是(left + right + 1) >> 1]
大于还是小于 - [这个对应上下界问题]
取不取等于 - [是大于等于还是小于等于]
第一个还是最后一个 - [找第一个大于目标的位置还是找最后一个大于目标的位置]
每个选项都是两种可能,于是二分写法一共有2^6=64种写法。也就是说,从这六个选项中的每个选项中任意挑选一个就可以组成一个二分的问题。
那么这么多种类的二分,是不是每种二分都要去记呢?肯定不要啊,接下来我会告诉你一个通用的方法。
整数二分
二分查找
二分查找也有很多情况,例如在有序数组中找某一个数(记为a)的位置,如果只有一个数,那直接返回这个数的位置就好了,但是如果存在好几个相同的该数字,二分又可以分为找第一个出现的位置(即数组中第一个大于等于a的数的位置)或者最后一个出现的位置(即第一个大于a的数的位置的前一位),这两种就是所谓的下界和上界,这两种我们放到二分答案的情况里说明
这里我们先看每种数只有一个的情况,假设a数组元素严格递增
int binarySearch(int *a, int l, int r, int key)//key是我们要找的数
{
while(l < r)
{
int mid = (l + r) / 2;
if(a[mid] < key) l = mid + 1;
else if(a[mid] == key) return mid;
else r = mid;
}
return -1;
}
这就是最简单的二分了,我们来具体看看它的计算过程
首先该写法它的 l 和 r,即我们二分的区间的左右端点,它是保证要找的数在[l, r)里的,也就是区间左端点可能是我们要找的数,而区间右端点不可能是
当区间中点mid偏小时,l = mid + 1,二分区间变成[mid + 1, r),如果该数字存在,必定还在该区间内;
如果mid偏大,r = mid,mid不可能是我们要找的数,而r端点本来就不会是答案,所以可以这么写
所以每次二分区间[l, r),区间必定会缩小,不可能死循环,最后一次判定时(如果一直没找到该数),区间变成[l, l + 1),这时候mid = l,如果mid还不等于我们要找的数,区间就会缩小到 l == r ,便退出了循环,返回-1表示没找到
虽然该写法没什么大问题,而且比较简洁,但是我们需要注意:
mid = (l + r) / 2 ,l + r可能会超过int范围,我们也可以写成 l + (r - l) / 2,可以了防止溢出,并且具有更好的通用性(如果 l 和 r 是迭代器或者指针,第一种就不行了, 因为迭代器或指针是不能相加的)
二分求上界
当我们设置二分潜在的答案区间为左闭右开的时候,即 [l, r),最终得到的 l 就是答案的上界,因为这时候 l 的右侧答案都已经不符合要求,l必定是答案中最大的了
int upperBound(int l, int r)
{
while(l + 1 < r) //循环条件为区间长度>=2
{
int mid = (l + r) / 2;
if(check(mid)) l = mid; //检查mid是否符合题目要求
else r = mid;
}
return l;
}
循环结束后区间大小就是 l,即 [l, l+1),最终答案就是 l
但是这样的写法需要注意以下3点:
- 初始二分的潜在的答案区间的右端点r要设置成比最大可能答案大,因为r不会被访问到
- 还是 l + r 可能溢出
- 如果题目说了可能没有答案,最后需要检查下 l,因为可能二分的区间里没有答案,每次循环都是 r = mid,最终的l没有被检查过
二分求下界
当我们设置二分潜在的答案区间为左开右闭的时候,即 (l, r],最终得到的 r 就是答案的下界,因为这时候 r 的左侧答案都已经不符合要求,r必定是答案中最小的了
int lowerBound(int l, int r)
{
while(l + 1 < r) //循环条件为区间长度>=2
{
int mid = (l + r) / 2;
if(check(mid)) r = mid; //检查mid是否符合题目要求
else l = mid;
}
return r;
}
同样的我们也需要注意以下三点:
但是这样的写法需要注意以下3点:
- 初始二分的潜在的答案区间的左端点l要设置成比最小可能答案大小,因为l不会被访问到
- 还是 l + r 可能溢出
- 如果题目说了可能没有答案,最后需要检查下 r,因为可能二分的区间里没有答案,每次循环都是 l = mid,最终的r没有被检查过
浮点数二分
浮点数二分基本不会有什么问题,因为不会有整数二分取整没取好导致死循环的问题
有两种写法:
以循环次数为循环终止条件
for(int i = 0; i < 60; ++i)//循环60次就已经可以达到很高的精度了
{
mid = (l + r) / 2;
//检查mid
}
以精度位循环终止条件
while(r - l < eps)//eps为题目要求的精度
{
mid = (l + r) / 2;
//检查mid
}
比较推荐第一种写法,因为第二种如果精度设置过小,加上浮点数的精度问题还是可能死循环
二分练习
给你一个序列,然后给你m个元素,让你从序列中找出与每个元素最接近的数字输出来,如果有两个就输出两个。
Input
多组输入,第一行给你两个数n(0 < n < 10000000),m(0 < m < n),接下来是数列的n个数,然后再输入m个元素,让你找出最接近每个元素的值。如果有两个,按从小到大输出。
Output
这m个数分别输出最接近每个元素的值,组与组之间输出一个空行。
Sample Input
8 4
1 2 3 4 5 6 8 11
4
9
2
7
Sample Output
4
8
2
6 8
#include <stdio.h>
#include <bits/stdc++.h>
#define Maxn 10000000
using namespace std;
int a[Maxn];
int up(int l,int r,int key)
{
int k=-1;
while(l<=r)
{
int mid=(l+r)>>1;
if(a[mid]<key)
l=mid+1;
else
r=mid-1,k=a[mid];
}
return k;
}
int down(int l,int r,int key)
{
int t=-1;
while(l<=r)
{
int mid=(l+r)>>1;
if(a[mid]<=key)
{
l=mid+1;
t=a[mid];
}
else
r=mid-1;
}
return t;
}
int main()
{
int n,m,i,k,t,ans;
while(~scanf("%d%d",&n,&m))
{
for(i=0; i<n; i++)
scanf("%d",a+i);
sort(a,a+n);
for(i=0; i<m; i++)
{
int key;
scanf("%d",&key);
k=up(0,n-1,key);
t=down(0,n-1,key);
if(k==-1)
printf("%d\n",t);
else if(t==-1)
printf("%d\n",k);
else if(k==t)
printf("%d\n",t);
else
{
if(k-key>key-t)
printf("%d\n",t);
else if(k-key==key-t)
printf("%d %d\n",t,k);
else
printf("%d\n",k);
}
}
printf("\n");
}
return 0;
}