简单查找

首先说明,博主还只是一个小菜鸟,趁这个机会回顾一下最近学习的思想和算法,有任何错误以及改进之处,敬请指正:

  • 二分法
  • 三分法
  • 尺取法

  • 需要注意的是,简单查找必须要按照关键字信息先排序,我一般是用sort,也可以按照自己的个人习惯排序。

二分查找

折半查找法也称为二分查找法,它充分利用了元素间的次序关系,采用分治策略,可在最坏的情况下用O(log n)完成搜索任务。它的基本思想是,将n个元素分成个数大致相同的两半,取a[n/2]与欲查找的x作比较,如果x=a[n/2]则找到x,算法终止。如 果x小于a[n/2],则我们只要在数组a的左半部继续搜索x(这里假设数组元素呈升序排列)。如果x大于a[n/2],则我们只要在数组a的右半部继续搜索x。 —— [百度百科 ]

二分查找可以分为整型和浮点型,算法思路基本相同,每次将问题分为两部分,每一次循环就能去掉其中的一半,时间复杂度是O(logn)主要步骤:
1.判断上界和下界作为边界值
2.跳出循环的条件
3.相同值的处理

不多说了,先上例题

整型51Nod-1267 4个数的和

给出N个整数,你来判断一下是否能够选出4个数,他们的和为0,可以则输出”Yes”,否则输出”No”。
奉上链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=126

这道题与给出四个数组不同,它还要求每个元素最多只能使用一次,也就是说另外要记录已经使用过的元素,保证它不被二次使用,博主是用了一个结构体记录位置,听大佬说还可以使用map来着,但是博主比较菜,没听懂,囧!

代码

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<math.h>
long long a[1005];
struct coor
{
    int x;
    int y;
    long long sum;
}su[1000000];   //用结构体中的x,y记录组成sum的两个元素
int cmp(coor x,coor y);
int binary_search(int n)
{
    long long right,left;int bug=0;
    for(int k=0;k<n-1;k++)
        for(int s=k+1;s<n;s++)
        {
            left=0;
            right=n*(n-1)/2;
            while(left+1<right)/*注意这里,跳出循环的条件,如果写成left<right,需要在内部进行更多判断*/
            {
                long long mid=(right+left)/2;
                if((su[mid].x!=k) && (su[mid].y!=k) &&(su[mid].x!=s) &&(su[mid].y!=s))
                {
                    if(su[mid].sum+a[k]+a[s]==0)
                    {
                        bug=1;
                        break;
                    }
                    else if(su[mid].sum>(-a[k]-a[s]))/*注意状态的变化,right和left的变化不要写反了,搞不清楚建议用笔演算一下*/
                        right=mid;
                    else
                        left=mid;
                }
                else
                    break;
            }
        }
    if(bug==1) return true;
    else return false;
}
using namespace std;
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%I64d",&a[i]);
    int tot=0;
    for(int i=0;i<n-1;i++)
    {
        for(int j=i+1;j<n;j++)
        {

            su[tot].sum=a[i]+a[j];
            su[tot].x=i;
            su[tot].y=j;
            tot++;

        }
    }
    sort(su,su+(n*(n-1)/2),cmp);
    if(binary_search(n))
        printf("Yes");
    else
        printf("No");

    return 0;
}
int cmp(coor x,coor y)
{
    return x.sum<y.sum;
}

浮点型 HDU-2289 CUP

题目大意:已知圆台本身的底面半径r和顶面半径R,以及本身的高H,现在往里面注入V体积的水,求水的高。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2289
这道题数学比较好的人可以直接根据相似几何体的关系,直接求高,博主已经亲身实验过了,可以,但是很烦(可能是博主的数学比较水),这里我建议是二分查找水的高

代码

#include<stdio.h>
#include<math.h>
#define PI 3.1415926535
double tmp,r;
double eps=0.00000001;
double cal(double n);
int main()
{
    int T;
    scanf("%d",&T);
    for(int i=0;i<T;i++)
    {
        double R,h,V;
        scanf("%lf%lf%lf%lf",&r,&R,&h,&V);
        double left=0,right=h; /*敲黑板,上界下界一定要考虑清楚,博主就是因为把下界写成了圆台本身的高下界1,所以wa了好几发*/
        tmp=(R-r)*1.0/h;
        while(fabs(right-left)>eps)/*这是其中一种写法,也可以int time=100,while(time--)*/
        {
            double mid=(left+right)*1.0/2;
            if(fabs(cal(mid)-V)<eps) 
                break;
            if(cal(mid)<V)
               left=mid;
            else
                right=mid;
        }

        printf("%.6lf\n",right);
    }
    return 0;
}
double cal(double n)//圆台的体积计算公式
{
    double R_mid=n*tmp+r;
    double V_mid=PI/3.0*(r*r+R_mid*R_mid+R_mid*r)*n;
    return V_mid;
}

二分法基本就告一个段落了,库函数里的lower_bound和upper_bound,有兴趣的小伙伴也可以去学一学

三分法

遇到凸型或凹型函数时,可以用三分查找求那个凸点或者凹点,基本步骤和二分法其实没什么区别,我就不多说了。这里写图片描述
如图,找到两个最边界(极限状态就是两边都是无穷大),找到mid1,据说黄金分割点是最佳状态,但是博主更喜欢直接取半,再将mid1和right相加取半,可以保证mid2在mid1和right之间,这样就不会错过那个最凹(凸)点。最后是要确定跳出循环的条件,一般循环个一百次或者保证左右相差不超过某个很小的值。
这里给出三分法的套路:

while(fabs(r-l)>0.0000001)/*int time=100,while(time--)*/
{
    double mid1=(r+l)/2;
    double mid2=(mid1+r)/2;
    ans1=cal(mid1);
    ans2=cal(mid2);
    if(ans1<ans2)
        l=mid1;
    else
        r=mid2;
}

例题

CodeForces-780B The Meeting Place Cannot Be Changed

题目大意:数轴上有N个点,第i个人站在Xi的位置,每个人以Vi的速度移动,求能够使所有人以最短时间汇合的地点坐标
题目链接:http://codeforces.com/problemset/problem/780/B
考虑极限状态,如果该地点在无穷小或者在无穷大,所用的时间应该是无穷大,那么可以充分相信,地点应该是在最右边和最左边的人之间,以此为边界进行计算。

这道题套公式就可以了,博主用了个结构体记录坐标和速度,以此求最短时间。

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<math.h>
#define exp 0.000001
using namespace std;
struct friends
{
    int coor;
    int speed;
}fri[60005];
int cmp(friends x,friends y);
double cal(double place);
double tmp;
int num;
int main()
{

    double right=0,left=0,ans1,ans2;
    scanf("%d",&num);
    for(int i=0;i<num;i++)
        scanf("%d",&fri[i].coor);
    for(int i=0;i<num;i++)
        scanf("%d",&fri[i].speed);
    sort(fri,fri+num,cmp);
    left=fri[0].coor*1.0;
    right=(fri[num-1].coor)*1.0;
    while(fabs(right-left)>exp)
    {
        double mid1=(left+right)/2.0;
        double mid2=(mid1+right)/2.0;
        ans1=cal(mid1);
        ans2=cal(mid2);
        if(ans1>ans2)
            left=mid1;
        else
            right=mid2;
    }
    printf("%lf",cal(left));

    return 0;
}
int cmp(friends x,friends y)
{
    return (x.coor<y.coor);
}
double cal(double place)
{
    tmp=0;
    for(int i=0;i<num;i++)
    {
        double timed=fabs(fri[i].coor-place)*1.0/fri[i].speed;
        tmp=max(tmp,fabs(fri[i].coor-place)*1.0/fri[i].speed);
    }
    return tmp;
}

尺取法

尺取法:顾名思义,像尺子一样取一段,尺取法通常是对数组保存一对下标(起点和终点),然后根据实际情况交替推进两个端点直到得出答案。尺取法比暴力枚举的效率要高很多,特别是在数据量比较大的情况更需要用尺取法。

综上,尺取法的要注意的以下几点:
1.什么时候能用尺取法?
(所选区间有一定规律,并且能根据这个规律推进左右端点)
2.什么时候推进端点?
(不断扩大右端点,直到满足条件,在满足条件后扩大左端点+1,继续扩大右端点,重复直到左右端点走到最后,选取最小区间)

这时候又是例题时间,直接通过例题感受一下尺取法的魅力吧。

51Nod-1127最短的包含字符串

题目:给出一个字符串,求该字符串的一个子串S,S包含A-Z中的全部字母,并且S是所有符合条件的子串中最短的,输出S的长度。如果给出的字符串中并不包括A-Z中的全部字母,则输出No Solution。
题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1127

另左右端点都在数组s[0]的位置,不断推进右端点,并且记录其中每一个字母出现的次数,直到26个字母都出现了,此时right不动,再看左端点,只要该字母出现的次数已经超过一了,就不断推进左端点,直到left对应的字母出现次数等于1,推进右端点,通过ans记录right-left的最小值

代码

#include<stdio.h>
#include<string.h>
char s[100005];
int num[26]={0};
int main()
{
    scanf("%s",s);
    int left=0,right=0,len,ans=100005,acount=0;
    len=strlen(s);
    while(left<=right && right<len)
    {
        if(num[s[right]-'A']==0)
            acount++;
        num[s[right]-'A']++;
        while(num[s[left]-'A']>1)
        {
            num[s[left]-'A']--;
            left++;
        }
        if(acount==26)
        {
            ans=ans>(right-left+1)?(right-left+1):ans;
        }
        right++;
    }
    if(ans!=100005)
        printf("%d",ans);
    else
        printf("No Solution");
    return 0;
}

好啦,今天就到这里啦!无论是二分、三分还是尺取,博主还是有很多要学习的地方,上文有任何不对的地方,希望大家指正啦~~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值