目录
二分查找(查找有序数列中的某个值)
设算法的输入实例中有序的关键字序列为
(05,13,19,21,37,56,64,75,80,88,92)
1 2 3 4 5 6 7 8 9 10 12
顺序查找:如果按顺序查找,分析一下查找成功的时间复杂度。查找数值21,你能模拟一下么?
二分查找算法分析
① 执行过程
二分查找过程可用二叉树来描述:把当前查找区间的中间位置上的结点作为根,左子表和右子表中的结点分别作为根的左子树和右子树。由此得到的二叉树,称为描述二分查找的判定树(Decision Tree)或比较树(Comparison Tree)。
② 二分查找的平均查找长度
设内部结点的总数为n=2h-1,则判定树是深度为h=lg(n+1)的满二叉树(深度h不计外部结点)。树中第k层上的结点个数为2k-1,查找它们所需的比较次数是k。因此在等概率假设下,二分查找成功时的平均查找长度为:
ASLbn≈lg(n+1)-1
二分查找在查找失败时所需比较的关键字个数不超过判定树的深度,在最坏情况下查找成功的比较次数也不超过判定树的深度。即为:
二分查找的最坏性能和平均性能相当接近。
代码:binary_research()
#include <iostream>
using namespace std;
const int N =10;
int a[12]={0 ,5, 13,19, 21,37,56,64,75,80,88,92};
int my_research(int L, int R, int key){
int first = L, last = R-1;
while(first < last){
int middle = (first+last)/2;
if( a[middle]==key)
return middle;
else
if( a[middle] < key ) first = middle+1;
else last=middle-1;
}
return -1;
}
int main()
{
cout << my_research(1, 12,44);
return 0;
}
二分法的注意事项:
(05,13,19,21,37,56,64,75,80,88,92)
1 2 3 4 5 6 7 8 9 10 12
如果要查找有序数组里面的元素V, 然而数组里面有多个元素都是V。
lower_bound : 算法返回一个非递减序列[ first, last )中的第一个大于等于值V的位置。当V存在时,返回出现的第一个位置。 若果不存在,则返回一个这样的下标: 在此处插入V,序列依然有序
#include <iostream>
using namespace std;
const int N =10;
int a[12]={0 ,5, 13,21, 21,21,21,64,75,80,88,92};
int my_lower_bound(int L, int R, int key){
int first = L, last = R-1;
while(first < last){
int middle = (first+last)/2;
if( a[middle] >= key ) last = middle;
else first = middle+1;
//这里要注意,不能是middle 若果是middle,[middle, last] 可能与[fist, last]区间一样,将发生死循环
}
return first;
}
int main()
{
cout << my_research(1, 12,21);
return 0;
}
若 a[middle] ==key ,至少已经找到一个,然后左边可能还有, 因此区间变成 【first , middle】
若 a[middle] > key ,所找的位置,不可在后面。 若找不到,有可能是middle位置, 因此区间变成 【first , middle】
若 a[middle] < key ,所找的位置不可能是middle,也不可能在前面 因此区间变成 【middle+1, last】
upper_boudn:算法返回一个非递减序列[ first, last )中第一个大于val的位置。当V存在时,返回出现的最后一个位置的下一个位置。若果不存在,则返回一个这样的下标: 在此处插入V,序列依然有序
如果想得到值等于V的完整区间呢? lower_bound 返回L位置, upper_bound返回R位置。 [ L, R ) ,若果L=R,区间为空。
#include <iostream>
using namespace std;
const int N =10;
int a[12]={0 ,5, 13,21, 21,21,21,64,75,80,88,92};
int my_upper_bound(int L, int R, int key){
int first = L, last = R-1;
while(first < last){
int middle = (first+last)/2;
if( a[middle] <= key ) first = middle+1;
else last = middle;
}
return first;
}
int main()
{
cout << my_research(1, 12,21);
return 0;
}
实数区域上的二分
确定好精度 以 L + esp < R 为循环条件 ,一般需要保留2位小数时候,则取精度eps = le-4
while(L+ esp < R){
double mid = (L + R) /2
if ( ) R = middle else L = middle
}
或者干脆采用循环固定次数的方法,也是不错的策略
for(int i=0;i<100;i++){
double middle = (L+R) /2;
if() L=middle; else R =middle
}
二分答案(不只是查找值)
题目描述中若出现类似: “最大值最小”的含义,这个答案就具有单调性,可用二分答案法。
这个宏观的最优化问题,可以抽象为一个函数,其“定义域”是该问题的可行方案。
考虑“求某个条件C(x)的最小x” ,这个问题,对于任意满足C(x)的x,如果所有的x’ > x 也满足C(x’)的话,这个问题可以想像成一个特殊的单调函数,在s的一侧不合法,在s的另一侧不合法,我们就可以用二分法找到某得分界点。
例题:POJ 1905
热胀冷缩(expanding.in/out/pas/c/cpp) (POJ 1905)
我们知道,一般情况下,细金属棒加热后会膨胀变长。比如,某种长度为L的金属棒,当加热n度后,它的新长度为S = (1 + n * C) * L,其中C是热碰撞系数。
现在我们将此金属棒固定在两块刚性墙体上并加热,金属棒受热膨胀后称为一段圆弧。
现在请你计算圆弧中心点距离原中心点的距离。
【输入数据】
输入数据若干行,每一行,包括三个用空格隔开的非负数字L、n和C。输入数据保证金属棒受热膨胀后的新长度不会超过原长度的1.5倍。
数据结束以 三个-1结束。
【输出数据】
输出数据每行一个数字,表示膨胀后圆弧的中心点离开原中心点的距离,保留三位小数。
Sample Input
1000 100 0.0001
15000 10 0.00006
10 0 0.001
-1 -1 -1
Sample Output
61.329
225.020
0.000
#include<stdio.h>
#include<math.h>
const double esp=1e-5;
int main()
{
double l,n,c,s;
double r;
while(scanf("%lf%lf%lf",&l,&n,&c))
{
if(l<0&&n<0&&c<0)
{
break;
}
double low=0.0;
double high=0.5*l;
double mid;
s=(1+n*c)*l;
while(high-low>esp)
{
mid=(low+high)/2;
r=(4*mid*mid+l*l)/(8*mid);
if( 2*r*asin(l/(2*r)) < s )
low=mid;
else
high=mid;
}
printf("%.3f\n",mid);
}
return 0;
}
例题1: 切割绳子: https://www.luogu.org/problemnew/show/P1577
#include<bits/stdc++.h>
using namespace std;
int n,k;
double a[10005],l,r,mid;
char s[100];
inline bool check(double x)
{
int tot=0;
for(int i=1;i<=n;i++)tot+=floor(a[i]/x);
return tot>=k;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)scanf("%lf",&a[i]),r+=a[i];
while(r-l>1e-4)
{
mid=(l+r)/2;
if(check(mid))l=mid;
else r=mid;
}
sprintf(s+1,"%.3f",l);
s[strlen(s+1)]='\0';
printf("%s",s+1);
return 0;
}
例题2: 有N本书排成一行,已知第 i 本书的厚度是Ai。把它们分成连续的M组,使T最小化,其中T表示厚度之和最大的一组的厚度。
解析:题目出现了类似“最大值最小”的含义,可以看出答案具有单调性(也就是说这个答案满足在一定单调区间内)。所以可以使用二分搜索,而判定函数如何写呢?假设最大组为size,一共有m组,那么m*size一定小于所有厚度之和;那我们顺序用size减去每本书的厚度,看看能分为几组和小于size且最大,这样一来如果组数小于m,那我们可以通过将一组分割成好几组来凑到m,此时最大厚度不变。而如果组数大于m,我们则可以通过合并来使组数等于m,但这样一来最大厚度就必然大于size。而答案必然存在于1到inf(无穷大),故我们只需找到临界点,即为答案。
bool check(int size){
int cnt = 1;
int tmp = size;
for(int i = 0;i < n;i++){
if(tmp - a[i] >= 0) tmp -= a[i];
else cnt++,tmp = size - a[i];
}
return cnt <= m;
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 0;i < n;i++) scanf("%d",a+i);
int l = 0,r = inf;
while(l < r){
int mid = (r + l )/2;
if(check(mid)) l = mid + 1;
else r = mid;
}
printf("%d\n",l);
return 0;
}
例题3:愤怒的牛 POJ 2456
/*
题意:
有n个牛栏,选m个放进牛,相当于一条线段上有 n 个点,选取 m 个点,
使得相邻点之间的最小距离值最大
思路:贪心+二分
二分枚举相邻两牛的间距,判断大于等于此间距下能否放进所有的牛。
*/
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 1e6+10;
int a[N],n,m;
bool judge(int k)//枚举间距k,看能否使任意两相邻牛
{
int cnt = a[0], num = 1;//num为1表示已经第一头牛放在a[0]牛栏中
for(int i = 1; i < n; i ++)//枚举剩下的牛栏
{
if(a[i] - cnt >= k)//a[i]这个牛栏和上一个牛栏间距大于等于k,表示可以再放进牛
{
cnt = a[i];
num ++;//又放进了一头牛
}
if(num >= m) return true;//所有牛都放完了
}
return false;
}
void solve()
{
int l = 1, r = a[n-1] - a[0];//最小距离为1,最大距离为牛栏编号最大的减去编号最小的
while(l < r)
{
int mid = (l+r) >> 1;
if(judge(mid)) l = mid + 1;
else r = mid;
}
printf("%d\n",r-1);
}
int main()
{
int i;
while(~scanf("%d%d",&n,&m))
{
for(i = 0; i < n; i ++)
scanf("%d",&a[i]);
sort(a, a+n);//对牛栏排序
solve();
}
return 0;
}