直通BAT-二分查找

1 简介与实例

在有序数组中查找某一数据所在的下标,若存在该数据,返回下标,否则返回-1。

#include <cstdio>

int a[10]={1,3,5,7,9,11,13,15,17,19};
int value;   //待查找元素的值
int index;  //查找到的下标,如果没有查到,返回-1

int search_index(int a[],int n)
{
    int l=0;
    int r=n-1;
    while(l<=r)
    {
        int mid=l+((r-l)>>2);
        if(a[mid]==value)
            return mid;
        else if(a[mid]>value)
            r=mid-1;
        else
            l=mid+1;
    }
    return -1;
}

int main()
{
    printf("请输入您要查询的数据:\n");
    while(scanf("%d",&value)!=EOF)
    {
        index=search_index(a,10);
        printf("您所查询的数据%d所在的下标为:%d\n",value,index);
    }
    return 0;
}

以上是左闭右闭的写法,所谓左开右闭就是在[0,n-1]的区间查找,区间的左右边界都可以取到。

#include <cstdio>

int a[10]={1,3,5,7,9,11,13,15,17,19};
int value;   //待查找元素的值
int index;  //查找到的下标,如果没有查到,返回-1

int search_index(int a[],int n)
{
    int l=0;
    int r=n;
    while(l<r)
    {
        int mid=l+((r-l)>>2);
        if(a[mid]==value)
            return mid;
        else if(a[mid]>value)
            r=mid;
        else
            l=mid+1;
    }
    return -1;
}

int main()
{
    printf("请输入您要查询的数据:\n");
    while(scanf("%d",&value)!=EOF)
    {
        index=search_index(a,10);
        printf("您所查询的数据%d所在的下标为:%d\n",value,index);
    }
    return 0;
}

以上是左闭右开的写法,即在[0,n)的范围查找,右边的数字取不到。注意这种写法与左闭右闭写法的边界调整的区别。

2 二分的应用,变种与案例

二分查找常见的应用场景:

1 在有序序列中查找一个数,比如上边的例子。

2 并不一定非要在有序序列中得到应用,任意一个序列,只要在二分之后可以淘汰掉一半,那么就可以采用二分法。比如腾讯秋招笔试题目-贪吃的小Q

二分查找常见的考察点:

1 对于边界条件的考察以及代码实现的能力。

2 二分搜索常见题目的变化:

(1)给定处理或查找的对象不同。比如在有重复数值的数组中查找和在无重复数值的数组中查找时,处理情况的细节稍显差异。

(2)判断条件不同。一般需要查找一个数组中等于X的位置,我们也可以查找数组中大于或小于X的位置。

(3)要求返回的内容不同。返回任意一个等于X的位置,返回最早等于X的位置,或者返回等于X的个数等。

3 在有序循环数组中进行二分查找:

     比如数组12345循环之后可以是:12345  23451  34512  45123  51234

二分搜索的重要提醒:

在选择中间节点时,选择的公式为mid=left+(right-left)/2,   防止数据过大时数据溢出。

案例一

 

本题可以用二分法解决。

1 如果数组为空或长度为0,返回-1,表示局部最小位置不存在。

2 如果数组长度为1,返回0,因为0便是局部最小位置。

3 如果数组长度大于1,先检查2头的位置。即:

(1)如果a[0]<a[1],返回0

(2)如果a[n-1]<a[n-2],返回n-1

4 如果2头不满足,说明a[0]>a[1],a[n-1]>a[n-2],所以从最左的位置向右看,有减小的趋势,从最右的位置向左看,也是减小的趋势。此时查看mid位置的数据:

(1)如果Mid位置的数据既比左边小,又比右边小,则为局部最小值。

(2)如果Mid的数据比右边小,比左边大,则mid往左是减小的趋势。取左部分继续查找。

(3)如果Mid的数据比左边小,比右边大,则Mid往右是减小的趋势,取右部分继续查找。

 (4)如果mid的数据比左右都大,任选一侧都可以。

#include <cstdio>

const int maxn=20;
int n;
int a[maxn];
int index;

int find_part_min_index(int a[],int n)
{
    if(n<=0)
        return -1;
    else if(n==1)
        return 0;
    else if(a[0]<a[1])
        return 0;
    else if(a[n-1]<a[n-2])
        return n-1;

    int left=0;
    int right=n-1;
    while(left<=right)
    {
        int mid=left+(right-left)>>1;
        if(a[mid]<a[mid-1]&&a[mid]<a[mid+1])
            return mid;
        else if(a[mid]>a[mid-1]&&a[mid]<a[mid+1])
            right=mid-1;
        else if(a[mid]>a[mid+1]&&a[mid]<a[mid-1])
            left=mid+1;
        else
            left=mid+1;
    }
    return -1;
}

int main()
{
    while(true)
    {
        printf("请输入数组的元素个数:\n");
        scanf("%d",&n);
        printf("请输入数组的元素:\n");
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
        }
        index=find_part_min_index(a,n);
        printf("数组的局部最小位置下标为:%d\n",index);
        printf("\n");
    }
    return 0;
}

案例二

给定一个有序数组arr,再给定一个整数num,请在数组中找到数字num这个数出现的最左边的位置。

思路:定义一个全局变量ans,用变量记录数据num最后一次出现的位置。如果最后没有找到,就返回-1.

#include <cstdio>

const int maxn=20;
int ans;
int a[maxn];
int n;
int num=3;

int find_num_of_min_index(int a[],int n)
{
    if(n<=0)
        return -1;
    int left=0;
    int right=n-1;

    while(left<=right)
    {
        int mid=left+(right-left)/2;

        if(a[mid]==num)
        {
            ans=mid;
            right=mid-1;
            continue;
        }

        else if(a[mid]>num)
        {
            right=mid-1;
        }
        else
            left=mid+1;
    }

    if(a[ans]==num)
        return ans;
    else
        return -1;
}

int main()
{
    while(true)
    {
        printf("请输入数组的元素个数:\n");
        scanf("%d",&n);
        printf("请输入数组的元素:\n");
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
        }
        int index=find_num_of_min_index(a,n);
        printf("数组的关于数据%d的最小位置下标为:%d\n",num,index);
        printf("\n");
    }
    return 0;
}

案例三

给定一个有序循环数组,返回数组中的最小值。{3,4,5,1,2}

首先判断a[0]是否小于a[n-1],如果小于,说明数列已经有序,直接返回a[0]即可。

否则,a[0]>=a[n-1]:说明是逆序的:

(1)如果a[0]>a[M],则在0-m的范围找;(注意不是m-1)

(2)如果a[n-1]<a[m]  ,则在m+1----n-1的范围去找

(3)否则,必有a[0]<=a[m],a[m]<=a[n-1],又a[0]>=a[n-1],则a[0]=a[n-1]=a[m]。这个时候的情况就如:2222222221222。只可以通过遍历来确定了。

#include <cstdio>
#include <iostream>
#include <string>
#include <sstream>
#include <vector>

using namespace std;

int solve(vector<int> &vec)
{
    if(vec.size()==0)
        return -1;
    if(vec.size()==1)
        return vec[0];
    if(vec[0]<vec[vec.size()-1])
        return vec[0];
    int left=0;
    int right=vec.size()-1;
    while(left<=right)
    {
        int mid=(right-left)/2+left;
        if(vec[left]>vec[mid])
            right=mid;
        else if(vec[mid]>vec[right])
            left=mid+1;
        else
        {
            int ans=vec[0];
            for(int i=1;i<vec.size()-1;i++)
            {
                if(ans>vec[i])
                    ans=vec[i];
            }
            return ans;
        }
    }
    return -1;
}

int main()
{
    string input;
    getline(cin,input);

    istringstream istr(input);
    int num;
    vector<int> vec;

    while(istr>>num)
    {
        vec.push_back(num);
    }

    int res=solve(vec);
    if(res!=-1)
        cout<<"数组中最小元素值为: "<<res<<endl;
    else
        cout<<"该数组为空数组!"<<endl;

    return 0;
}

 

案例四

给定一个有序数组arr,其中不含有重复元素,请找出满足arr[i]==i的条件最左的位置,如果所有位置上的数都不满足,返回-1。

思路如下:

(1)首先判断a[0]>=N-1,一定不存在。

(2)再判断a[N-1]<=0,同样不存在。

(3)观察中间元素:如果a[mid]>mid,right=mid-1;如果a[mid]<mid,left=mid+1;否则,记录该位置,继续right=mid-1;

#include <cstdio>

int res=-1;  //记录最后出现的位置
const int maxn=100;
int a[maxn];

int find_index(int a[],int n)
{
    if(n<=0)
        return -1;
    if(n==1)
    {
        if(a[0]==0)
            return 0;
        else
            return -1;
    }
    if(a[0]>=n-1)
        return -1;
    if(a[n-1]<=0)
        return -1;
    int left=0;
    int right=n-1;
    while(left<=right)
    {
        int mid=left+(right-left)/2;
        if(a[mid]>mid)
        {
            right=mid-1;
            continue;
        }

        else if(a[mid]>mid)
        {
            left=mid+1;
            continue;
        }

        else
        {
            res=mid;
            right=mid-1;
        }
    }
    return res;
}

int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
        }
        res=find_index(a,n);
        printf("%d\n\n",res);
    }
    return 0;
}

案例五

给定一颗完全二叉树头节点head,返回这棵树节点的个数,若完全二叉树节点数为N,请实现时间复杂度低于O(N)的解法。

首先找到二叉树最左边的节点,目的是统计出二叉树的高度/深度。

接着,找到二叉树头节点的右子树的最左边节点,如果该节点可以到达最后一层,说明头节点左子树是一个满二叉树。

这时,求出左子树节点个数加上根节点,右子树节点个数可以通过递归求取。

如果该节点不可以到达最后一层,说明头节点的右子树是一颗完全二叉树。这时候求右子树节点个数加上根节点,左子树节点个数可以通过递归求得。

其时间复杂度为O(logN^2)

参考博客:https://blog.csdn.net/qq_17550379/article/details/82052746

案例六

如何更快地求取一个整数k的N次方。若2个整数相乘并得到结果的时间复杂度为O(1),得到整数k的N次方的过程请实现时间复杂度为O(logN)的方法。

法一:

k^{N}=k^{N/2}*k^{N/2}    N为偶数

K^{N}=K^{(N-1)/2}*K^{(N-1)/2}*K    N为奇数

递归法:

#include <cstdio>

int base,exp;

int PowerWithExponent(int base,unsigned int exp)
{
    if(exp==0)
        return 1;
    if(exp==1)
        return base;
    int result=PowerWithExponent(base,exp>>1);
    result*=result;
    if(exp&0x1==1)
        result*=base;
    return result;
}

int main()
{

    printf("请输入底数和指数:\n");
    scanf("%d%d",&base,&exp);
    int result=PowerWithExponent(base,exp);
    printf("%d\n",result);

    return  0;
}

法二:

例如求10^{75},先把75写作二进制形式:1001011

10^{75}=10^{64}*10^{8}*10^{2}*10^{1}

那么我们可以先求出10^{1},10^{2},10^{4},10^{8},10^{16},10^{32},10^{64}。再根据二进制相应位置是否为1选择性地进行乘法。

代码如下:

题目为

#include <cstdio>
#include <iostream>
#include <cmath>

using namespace std;
typedef long long LL;

LL binaryPow(LL a, LL b,LL m)
{
    LL res=1;
    while(b>0)
    {
        if(b&1)
        {
            res=res*a%m;
        }
        a=a*a%m;
        b>>=1;
    }
    return res;
}

int main()
{
    LL a,b,m,res;
    cin>>a>>b>>m;
    res=binaryPow(a,b,m);
    cout<<res<<endl;
    return 0;
}

 

案例七  查找数组中第一个大于等于给定值的元素

#include <cstdio>
#include <iostream>
using namespace std;

int find_upper(int a[],int n)
{
    if(n==0)
        return -1;
    if(n==1)
    {
        if(a[0]>=3)
            return a[0];
        else
            return -1;
    }
    int left=0;
    int right=n-1;

    int res=-1;
    while(left<=right)
    {
        int mid=left+(right-left)/2;
        if(a[mid]<3)
            left=mid+1;
        else if(a[mid]>=3)
        {
            right=mid-1;
            res=a[mid];
        }

    }
    return res;

}

int main()
{
    int n;
    cin>>n;
    int a[n];
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
    }

    int num=find_upper(a,n);
    cout<<num;
}

案例八 求数值的近似值

根号2的近似值,精度为1e-5

#include <cstdio>
#include <iostream>
#include <cmath>

using namespace std;
const double eps=1e-5;

double f(double m)
{
    return m*m;
}

int main()
{
    double left=1;
    double right=2;
    double mid;
    while(right-left>eps)
    {
        mid=(left+right)/2;
        if(f(mid)>2)
            right=mid;
        else
            left=mid;
    }
    cout<<mid<<endl;
    return 0;
}

案例九 秋招猿辅导三面面试题:

一个二维蛇形矩阵,数值非递减,求一个数据在不在数组中。

1 2 3

6 5 4

7 9 11

 

思路:把二维看作一维,注意下标到真实位置的换算奥

#include <cstdio>
#include <iostream>
#include <vector>

using namespace std;

bool invector(vector<vector<int> > &vt,int val)
{
    if(vt.size()==0)
        return false;
    int n=vt.size();
    int m=vt[0].size();
    int left=0;
    int right=n*m-1;
    while(left<=right)
    {
        int mid=left+(right-left)/2;
        //奇数行,递减
        if(mid/m%2)
        {
            int data=vt[mid/m][m-mid%m-1];
            if(val==data)
                return true;
            else if(val>data)
            {
                left=mid+1;
            }
            else
                right=mid-1;
        }

        //偶数行,递增
        else
        {
             int data=vt[mid/m][mid%m];
            if(val==data)
                return true;
            else if(val>data)
            {
                left=mid+1;
            }
            else
                right=mid-1;
        }

    }
    return false;
}

int main()
{
    int n,m,val;
    cin>>n>>m>>val;
    vector<vector<int> > vt;
    for(int i=0;i<n;i++)
    {
        vector<int> temp(m,0);
        for(int j=0;j<m;j++)
        {
            cin>>temp[j];
        }
        vt.push_back(temp);
    }

    if(invector(vt,val))
    {
        cout<<val<<"在vt中"<<endl;
    }
    else
        cout<<val<<"不在vt中"<<endl;
    return 0;
}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章