直通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;
}

 

 

 

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