關於二分法問題個人理解

雖然二分法很簡單,但是之前並沒有對其有過太多的注意,只是把它當成一個查找元素的方法來應用,但是隨着後面做題的深入,發現二分法也有很多講究,所以這裏做一個總結歸納一下;

一、二分法的基礎概念:
二分法研究的序列可以分爲重複或者非重複序列,其序列要求都是遞增有序的;
對於非重複序列,我們可以很簡單的給出相應的推理和邏輯;
例如,我們如果要在一個嚴格遞增序列中查找給定的x,就可以有以下代碼:

int binarySearch(int A[],int left,int right,int x){
    int mid;
    while(left<=right){
        mid=(left+right)/2;
        if(A[mid]==x)
            return mid;
        else if(A[mid]>x){
            right=mid-1;
        }else{
            left=mid+1;
        }
    }
    return -1;
}

注意兩點:
1.判定條件left<=right,有的情況下寫成left<right,這個需要視情況而定;
2.當left和right達到一定程度的時候,如果簡單相加計算mid的時候,可能會導致溢出,所以往往比較保險的辦法就是:

mid=left+(right-left)/2

這樣就可以很好的避免溢出,從而保證能夠進行mid的計算;

上述針對的是遞增非重複序列,如果重複序列,我們又該作何思考?

例如示例中給出的問題,如果給出一個重複的遞增序列A,我們需要求出第一個大於等於x的位置L以及第一個大於x的元素的位置R;
這是兩個小問題,首先先進行第一個小問題的求解:找出第一個大於等於x的位置L;
其實對於這個問題,前提就已經默認了,必有L存在,所以判定條件就會發生相應的變化;

int lower_bound(int A[],int left,int right,int x){
    int mid;
    while(left<right){
        mid=(left+right)/2;
        if(A[mid]>=x){
            right=mid-1;
        }else{
            left=mid+1;
        }
    }
    return left;
}

這裏我們可以看出變了兩個條件,第一個是left<right,第二個是A[mid]>=x;
對於第一個條件的改變,我們可以理解爲把符合條件的元素夾出來,當循環終止的時候,必有left=right,此時兩個索引指向同一個元素,該元素就是我們在尋找的元素;
而對於第二個條件的改變,則是由於題目性質決定的;如果中間點大於等於x,則我們可以認爲要尋找的第一個x在mid的左邊,所以right=mid。即使在等於條件下,由於right=mid,所以還是可以保證要尋找的數在[left,right]區間內;這一點和之前的mid+1和mid-1完全不同,其根本原因是序列內元素是否唯一;
如果難以判別使用哪種方法,則每次及逆行mid迭代的時候,一定要試一試邊界是否能夠像預期那樣包括進行需要尋找的元素;

第二個自問題就是求序列中第一個大於x的位置;
對於這個問題,我們仍然需要關注的是判定條件;
代碼如下:

int upper_bound(int A[],int left,int right,int x){
    int mid;
    while(left<right){
        mid=(left+right)/2;
        if(A[mid]>x){
            right=mid;
        }else{
            left=mid+1;
        }
    }
    return left;
}

對於個算法,同樣的我們需要left==right來將符合的值夾出來;
當A[mid]>x時,由於我們尋找的時大於x的值,此時該值必在mid的左邊,同樣的,如果mid就是大於x的值,也應該包括進去,所以此時,right=mid;
當A[mid]<=x時,由於我們尋找的是一個大於x的值,所以mid不必包括進去,left=mid+1;

總的來說,上述推舉的方法,都在解決一個核心問題:在該序列中,找到第一個符合該條件的值;

二、二分法的主要應用:
1.利用二分法求某數字的近似值:
這個問題其實很經典,自己以前碰到過幾次;

例如:求解根號2的近似值,要求精度在10^-5;
這個問題就可以利用該方法進行計算;
首先我們需要構建目標函數F(x)=x^2,從而轉換成1~2區間內的實數計算;
設置邊界left=1,right=2,來利用函數進行逼近;
代碼如下:

const double eps=1e-5;
double f(double x){
    return x*x;
}
int binarySearch(){
    double left=1;
    double right=2;
    double mid;
    while(right-left>1e-5){
        mid=(right+left)/2;
        if(f(mid)>2){
            right=mid;
        }else{
            left=mid;
        }
    }
    return mid;
}

2.快速冪:
快速冪就是給出三個整數a,b,m,求得a^b%m;
這個問題也可以採用二分思想來進行遞歸計算;
若b是奇數:a^b=a*a^(b-1)
若b是偶數:a^b=a^(b/2)*a^(b/2);
代碼如下:

long long binarypow(long long a,long long b,long long m){
    if(b==0)
        return 1;
    if(b%2==1)
        return a*binarypow(a,b-1,m)%m;
    else{
        long long mul=binarypow(a,b/2,m);
        return mul*mul%m;
    }
}

注意上述的mul,不要單純計算兩次a^(b/2),而使用mul,這樣可以適當的介紹時間複雜度;

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