算法学习-二分查找和二分答案1

事情的起因是这样的,今天下午我打算在编程课做完一道二分查找的题,实现二分查找。很快啊,我啪地一下就就写完了啊。结果我大意了,洛谷不讲武德,一个tle就让我对二分查找进行了将近一个下午的修改(同时也学习了快写与快读)。



二分查找 ===

前言


  • 为什么要用二分查找?
    顺序查找暴力简单,但如果一个数组很大,而我们要查找的数又在后面,或者在数组中根本不存在,则查找的时间就会很长。这时就需要效率较高的二分查找。

  • 二分查找的原理是什么?
    一般情况下,我们不知道待查数据的分布情况,所以采用比较机械的办法,每次检查待查数据中排在最中间的元素,然后缩小范围,在前一半或后一半内继续查找,再找前一半或后一半中间的数……然后无限套娃。


    例如,对于以下一列递增的数 :

    12 14 18 20 21 22 24 26 28 30 33
    0 1 2 3 4 5 6 7 8 9 10

    要在途中找28,开始时,查找范围是0~10单元格,先从中间单元格,(0+10)/2=5号单元格里开始,记为里面的数为 arr[5] ,因为arr[5]<28,因为这一列数递增,则28在5号单元格后面,于是在(5+1=6)~10单元格查找,再次找到中间单元格8,因为arr[8] 等于 28,而28正是我们要找的数,于是查找就结束了,8 就是我们要找的数的位置。
    如果我们要找23,一个不存在的数,同样去推导,最后查找范围会变成[6,5],显然不成立,于是可以下结论,23不在这列数里。

代码实现


代码实现也简单,我啪——地一下就就打完了(其实花了一个小时),很快嗷。(用的是递归)

int seek(int a,int b,int num,int arr[])//a是查找的左端点,b是查找的右端点,num是要查找的数,arr[]是查找的对象。
{
        int mid=0;

        mid=(a+b)/2;

        if (arr[mid]==num) return mid;  //找到了就返回mid,也就是num的位置
        else if (arr[mid]>num&&(mid-1)>=a) return seek(a,mid-1,num,arr);    //如果出现(mid-1)<=a就是没有num,最后返回'-1'
        else if (arr[mid]<num&&(mid+1)<=b) return seek(mid+1,b,num,arr);    //如果出现mid+1>b就是没有num,最后返回'-1'

        return -1;  //没有 num 就返回-1;
}

(之后打的用循环实现的:)

do{
        mid=(a+b)/2;
        if(arr[mid]==num) break;
        else if(arr[mid]>num) b=mid-1;
        else a=mid+1;
    }while (a<=b);   //a,b依旧是左右端点。
if (a>b) cout<<'-1'<<' ';
else cout<<mid<<' '; 

深化拓展


当然这样就势必存在一个问题,如果我这个一列数虽然后一项不会比前一项小,但我可以相等啊,就比如

1 3 3 5 7 9 11 13 15 15
如果我们要找的是3,但是3有两个,我们输出哪个3的位置?一起输出吗?
有一个很笨的方法,找到其中一个三后,扫描其周围的的数,如果是3,将其下标一并给输出了,也可以存入数组先排序在输出。这很简单。

不过我做的题会tle!这很重要!

题目要求是,找到这一列数中第一次出现 num 的下标。
然后超时了,用快读和快写都无法解决,于是我认为这是原理性的问题,于是参照了一个题解,按他的循环思路打出了这个函数。
int seek(int a,int b,int num,int arr[])
{
        int mid=0;

        mid=(a+b)/2;

        if (arr[mid]>=num&&mid>a) return seek(a,mid,num,arr);
        else if (arr[mid]<num&&(mid+1)<b) return seek(mid+1,b,num,arr);
        else if(arr[mid]>=num&&mid==a) return a;
        else return b;
}

传递的变量都相同,不过返回值从“正确的下标”变成了 下标,判断是否存在交给主函数。

要点如下:

  • 我们要找的是第一个 num ,所以若存在相同,我们可以将第一个 num 后面的其他相同的数按照比 num 大处理,所以第一个 if 括号里 大于 变成了 大于等于。这样对于要查找的区间 [a,b] , 仍有 arr[a]<=num<=arr[b]。

  • 当如果就只有这一个 num , mid-1 就会直接跳过了,所以就不加一好了。这样对于要查找的区间 [a,b] , 仍有 arr[a]<=num<=arr[b]。

  • 这种写法有一个好处,那就是运行到最后,总会出现 mid=a 或者 mid+1=b 的情况(可以自己动手尝试,这两种情况对应最后两个else), 而对于要查找的区间 [a,b] ,总有 arr[a]<=num<=arr[b]。也就是对于区间 [a,b]外的对于所有的 i ,arr[i]都不等于num。所以当区间缩小到两端相等时,记它们的值为k,要么arr[k]=num,且这个k是第一次出现num的下标,要么arr[k]!=num ,这个时候也就是 num 不存在。其所以运行到最后返回 a或 b,再检查 arr[a] 或 arr[b] 是否与 num相等就行了.


    (当然,可能出现的特殊情况就是 num 不在这列数的最大值与最小值之间,不过也适用)

于是终于AC了


顺手打了 循环 版的,也是啪地一下,很快就打好了,这次是真的很快。

#include <iostream>
using namespace std;

int main()
{
   int n=0,m=0,i=0,arr[1005000]={0},a=0,b=0,num=0,mid=0;

   cin>>n>>m;//n是要查找对象的元素个数,m是要查找的 数 的个数

   for (i=1;i<=n;++i)
   {
       cin>>arr[i];
   }

   for(i=1;i<=m;++i)
   {
       cin>>num;

       a=1;
       b=n;
//以下是二分查找
       while(a!=b)
       {
           mid=(a+b)/2;
           if (arr[mid]>=num) b=mid;
           else a=mid+1;
       }

       if(arr[b]==num) cout<<b<<' ';
       else cout<<-1<<' ';
//以上是二分查找
   }

   return 0;
}

(这莫不简单多了,无能狂怒

没了。

最后


学习自:
洛谷P2249
C++程序设计|思想与方法(我的教材)

写博客好玩啊!

posted @ 2020-11-05 23:01  七铭的魔法师  阅读(115)  评论(0)    收藏  举报