雖然二分法很簡單,但是之前並沒有對其有過太多的注意,只是把它當成一個查找元素的方法來應用,但是隨着後面做題的深入,發現二分法也有很多講究,所以這裏做一個總結歸納一下;
一、二分法的基礎概念:
二分法研究的序列可以分爲重複或者非重複序列,其序列要求都是遞增有序的;
對於非重複序列,我們可以很簡單的給出相應的推理和邏輯;
例如,我們如果要在一個嚴格遞增序列中查找給定的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,這樣可以適當的介紹時間複雜度;