Maple
for me
FRIENDS
D&XWHQ

二分查找

2022-04-29 算法 查找算法

概述

这篇文章所要讲的二分查找,是一种效率很高的查找方法。二分查找可以将查找的时间复杂度降至*O(log2n)*,但是它要求所查找的数列至少是有序数列。在大部分题目中,这个数列会被规定为有序且连续,但在某些题目中,数列不是连续的,而如果无法查找到,则输出固定语句。这些大同小异,但是用二分查找的前提一定是该数列有序。

正文

具体示例

当我们在电话本中查找我们想要的人的电话号码时,我们通常知道想要查找的人的姓名,而电话本中记录的顺序完全按照姓名字母的字典序。我们通常如何查找?

先翻到电话本的一半,判断一半时候的姓名字母是在目标姓名字母的前边还是后边,然后再在目标姓名字母存在的部分继续上述过程,最终可以查到目标电话号码,这就是二分查找的具体应用之一。

原理

二分查找1.jpg

如图所示,在当前数列中查找6是否存在,若存在,输出其其中一个序号。

已知当前数列为非递减数列。首先,确定left为数列最左端,right为数列最右端。即left为1,right为11。设置mid变量,存储数列中点序号。在当前图所示状态下,mid取值为6,即6为该数列当前中点,只需比较所求数与中点所对应的数字。

由于9>6,可知所求数所对应序号只可能在6之前,也就是只可能存在于1到6之间。故用当前mid-1替换当前right。(之所以用mid-1是为了防止出现无限循环的情况)

二分查找2.jpg

此时mid为(1+5)/2即3。将所求数与序号3对应数字对比,5<6,故所求数所对应序号只可能在3后,也就是只可能存在于3到6之间。故用当前mid+1替换当前left。(原因同上)

二分查找3.jpg

此时mid为(4+5)/2即4。将所求数与序号4对应数字对比,发现正好相等。代表该数列中6确实存在,故可以输出目标数字对应的序号。

若left>right,代表给出的数列已经查找完,没有发现目标数字。

注意事项

  1. 应用二分查找法的前提一定是有序数列。
  2. 二分查找法在应用时应密切注意各变量的限值重叠交叉问题,不然可能会出现死循环。

标准代码

给出我手打的标准代码(默认给出的15个数有序)

#include<stdio.h>
int main()
{
    int l,r,i;
    int goal;
    int mid;
    int pd=0;
    int a[20];
    scanf("%d",&goal);
    for(i=1;i<=15;i++)
    {	
        scanf("%d",&a[i]);
    }
    l=1;
    r=15;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(a[mid]==goal)
        {
            printf("%d",mid);
            pd=1;
            break;
        }
        else if(a[mid]<goal)
        {
            l=mid+1;
        }
        else
        {
            r=mid-1;
        }
    }
    if(pd==0)
    {
        printf("no answer");
    }
    return 0;
}

通常的,每一个二分查找的题目中应包含两个条件。一个是判断找到的标准,一个是判断无法找到的精度。在我上面的代码中,找到的标准就是目标数字等于了当前判断的数字,代表二分查找已经查找到了。判断精度则是无限接近于0,原因是我判断的标准是当l>r时结束。在某些题目中,精度有明显给出,可以写成r-l<精度的格式。有时,判断精度也可通过固定循环次数代替。

相关题目

先提一句,在openjudge–noi上面有一个系列共十道二分法的题目。

查找最接近的元素–openjudge-noi

http://noi.openjudge.cn/ch0111/01/

二分查找4.1.jpg

基本是道裸二分查找,没啥好说的……

打出了ac代码,emmm我代码风格也不好,各人有各人的作法……

#include<stdio.h>
#include<math.h>
int a[100005]={-1};
int l,r;
int n,m,t;
int i;
int mid,ans,ans1;
int jdz(int cs)
{
    if(cs<0)
    {
        return -cs;
    }
    else
    {
        return cs;
    }
} 
int bj(int a1,int a2,int p)
{
    if(a1==-1)
    {
        a1=-999;
    }
    if(a2==-1)
    {
        a2=-999;
    }
    if(jdz(a1-p)<=jdz(a2-p))
    {
        return a1;
    }
    else
    {
        return a2;
    }
}
int cz(int q)
{	
    l=1;
    r=n;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(a[mid]<=q)
        {
            l=mid+1;
        }
        else
        {
            r=mid-1;
        }
    }
    mid=(l+r)/2;
    ans1=bj(a[mid],a[mid+1],q);
    a[0]=-1;
    a[n+1]=-1;
    return ans1;
}
int main()
{
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    scanf("%d",&m);
    for(i=1;i<=m;i++)
    {
        scanf("%d",&t);
        ans=cz(t);
        printf("%d\n",ans);
    }
    return 0;
} 

二分法求函数的零点–openjudge-noi

http://noi.openjudge.cn/ch0111/02/

二分查找4.3.jpg

也是道裸二分查找,跟上面那题难度相差不多,也就是个精度问题。

x的平方根–leetcode(力扣网)

https://leetcode-cn.com/problems/sqrtx/

二分查找4.2.jpg

这道题是一道二分答案的题目,可以看出精度部分可以取0.01,通过不断假设答案是当前值,然后不断二分得到最接近的结果,通过检验得出答案,不是很难……

高级应用

未完待续……

Author: Maple

Link: http://www.unknown9t.com/2022/04/29/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
常用技巧精选
NextPost >
个人常用OJ平台
CATALOG
  1. 1. 概述
  2. 2. 正文
    1. 2.1. 具体示例
    2. 2.2. 原理
    3. 2.3. 注意事项
    4. 2.4. 标准代码
    5. 2.5. 相关题目
      1. 2.5.1. 查找最接近的元素–openjudge-noi
      2. 2.5.2. 二分法求函数的零点–openjudge-noi
      3. 2.5.3. x的平方根–leetcode(力扣网)
    6. 2.6. 高级应用