二分查找

 

目录

 

顺序查找

二分查找(查找有序数列中的某个值)

二分答案(不只是查找值)


 

 

二分查找(查找有序数列中的某个值)

  设算法的输入实例中有序的关键字序列为

(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;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值