因爲常年二分寫成死循環,因此是時候有必要總結一下二分搜索了,這裏聲明一下本人的二分風格是左閉右開也就是[L,R)。node
這裏就不解釋什麼是二分搜索了,這裏將會介紹4種二分搜索,和二分搜索經常使用來解決的最小值最大化或者最大值最小化的問題,咱們都知道使用二分的最基本條件是,咱們二分的序列須要有單調性,這裏的序列是廣義性如:1.一個排好序的數組; 2.一個區間[L,R);3.其餘(暫時想不到)。因此下面介紹的時候會用v來表明咱們二分的目標,用第一個大於v,第一個大於等於v,最後一個小於v,最後一個小於等於v來描述,這裏能夠看到我即將要介紹的4種二分搜索。c++
這就是咱們常說的lower_bound()了,這是系統裏面自帶的庫函數,在數組或者一個vector容器中二分的時候,也就是否是必須手寫二分的時候首推使用這個,優勢代碼少,穩定(建議少裝逼,動不動手寫二分)。這裏咱們來介紹lower_bound()的使用方式。數組
首先是這個函數原型:less
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,const T& val, Compare comp)
其中first表明左邊界迭代器,last表明右邊界迭代器(注意左閉右開),val表明你要搜索的值,comp表明排序規則(這個參數在你對非結構體數組二分的時候並不須要,有默認規則)函數
實例:學習
int a[100]={1,2,3,3,4,5,5,6,8,9,22},n; while(cin>>n){ int p=lower_bound(a,a+11,n)-a; //若是a是vector,那麼lower(a.begin(),a.end(),v)-a.begin(); //你也能夠在指定在[L,R)區間內二分lower_bound(a.begin()+L,a.begin()+R,v)-a.begin(),數組也是同理的 cout<<p<<endl;//這裏輸出的是第一個大於等於n的數的下標 }
當對結構體數組進行二分搜索時(咱們能夠在這裏繼續輸入上面代碼的初始化的數據)spa
#include<bits/stdc++.h> using namespace std; struct node{ int x; }; int cmp(node a,node b){ return a.x<b.x;//注意這裏不能夠a.x<=b.x否則lower_bound就變成upper_bound了 } int main(){ int n;cin>>n; vector<node>a(n); node b; for(int i=0;i<n;i++) cin>>a[i].x; while(cin>>b.x){ int p=lower_bound(a.begin(),a.end(),b,cmp)-a.begin(); cout<<p<<endl;//輸出仍是下標 } return 0; }
這裏咱們介紹的是當數組是升序的時候的狀況,若是數組是降序的,咱們則須要從新定義排序規則,咱們這裏在使用lower_bound()就是尋找第一個小於等於v的下標。code
學會了如何使用庫函數,如今咱們來學習一下如何手寫一個lower_bound(),咱們知道二分有一個左邊界L和右邊界R,咱們定義[L,R)內的下標都小於v,咱們假設L爲當前區間的答案,R爲當前區間的實際答案(由於R是第一個大於等於v的下標),咱們每次二分的其實是爲了讓L和R不斷靠近,因此當L==R的時候,咱們假設的答案等於實際的答案,那麼就結束循環了,返回答案L。blog
#include<bits/stdc++.h> using namespace std; int main(){ int a[100]={1,2,3,3,4,5,5,6,8,9,22},v; while(cin>>v){ int L=0,R=11; while(L<R){ int M=(L+R)/2; if(a[M]>=v) R=M; else L=M+1; } cout<<L<<endl; } return 0; }
注:1.當a[M]>=n時,因爲R是第一個大於等於v下標,那麼R最大隻能是m排序
2.當a[M]<n時,說明[M,R)區間內的下標都是小於v的,L做爲最後的答案最小隻能是M+1
這就是咱們常說的upper_bound()了,這是系統裏面自帶的庫函數,這裏咱們來介紹upper_bound()的使用方式,和lower_bound()在可使用的時候推薦使用。
首先函數原型:
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,const T& val, Compare comp);
其中first表明左邊界迭代器,last表明右邊界迭代器(注意左閉右開),val表明你要搜索的值,comp表明排序規則(這個參數在你對非結構體數組二分的時候並不須要,有默認規則)
實例:
int a[100]={1,2,3,3,4,5,5,6,8,9,22},n; while(cin>>n){ int p=upper_bound(a,a+11,n)-a; //若是a是vector,那麼upper_bound(a.begin(),a.end(),v)-a.begin(); //你也能夠在指定在[L,R)區間內二分upper_bound(a.begin()+L,a.begin()+R,v)-a.begin(),數組也是同理的 cout<<p<<endl;//這裏輸出的是第一個大於等於n的數的下標 }
當對結構體數組進行二分搜索時(咱們能夠在這裏繼續輸入上面代碼的初始化的數據)
#include<bits/stdc++.h> using namespace std; struct node{ int x; }; int cmp(node a,node b){ return a.x<=b.x;//注意這裏不能夠a.x<=b.x否則upper_bound就變成lower_bound了 } int main(){ int n;cin>>n; vector<node>a(n); node b; for(int i=0;i<n;i++) cin>>a[i].x; while(cin>>b.x){ int p=upper_bound(a.begin(),a.end(),b,cmp)-a.begin(); cout<<p<<endl;//輸出仍是下標 } return 0; }
這裏咱們介紹的是當數組是升序的時候的狀況,若是數組是降序的,咱們則須要從新定義排序規則,咱們這裏在使用upper_bound()就是尋找第一個小於v的下標。
學會了如何使用庫函數,如今咱們來學習一下如何手寫一個upper_bound(),一樣的,咱們定義[L,R)內的下標都小於等於v,咱們假設L爲當前區間的答案,R爲當前區間的實際答案(由於R是第一個大於v的下標),咱們每次二分的其實是爲了讓L和R不斷靠近,因此當L==R的時候,咱們假設的答案等於實際的答案,那麼就結束循環了,返回答案L。
#include<bits/stdc++.h> using namespace std; int main(){ int a[100]={1,2,3,3,4,5,5,6,8,9,22},v; while(cin>>v){ int L=0,R=11; while(L<R){ int M=(L+R)/2; if(a[M]>v) R=M; else L=M+1; } cout<<L<<endl; } return 0; }
注:1.當a[M]>n時,因爲R是第一個大於v下標,那麼R最大隻能是M
2.當a[M]<=n時,說明[M,R)區間內的下標都是小於等於v的,L做爲最後的答案最小隻能是M+1
上面說過了,當數組爲降序的,使用lower_bound就是返回第一個小於等於下標,若一開始數組是升續的時候,那麼應該先reverse一下,再用lower_bound返回下標p,則在原數組中的下標爲n-p-1(假設數組有n個元素)。
這裏來介紹一下如何在若是手寫一個last_less_equal()。和lower_bound二分區間[L,R)左閉右開不一樣,last_less_equal()的二分區間爲(L,R]右閉左開。
一樣的,咱們定義(L,R]內的下標都大於v,咱們假設R爲當前區間的答案,L爲當前區間的實際答案(由於L是最後一個小於等於v的下標),咱們每次二分的其實是爲了讓L和R不斷靠近,因此當L==R的時候,咱們假設的答案等於實際的答案,那麼就結束循環了,返回答案L。
#include<bits/stdc++.h> using namespace std; int main(){ int a[100]={1,2,3,3,4,5,5,6,8,9,22},v; while(cin>>v){ int L=-1,R=10; while(L<R){ int M=(L+R+1)/2; if(a[M]<=v) L=M; else R=M-1; } cout<<L<<endl; } return 0; }
注:1.當a[M]<=n時,因爲L是最後一個小於等於v下標,那麼L最小隻能是M。
2.當a[M]>n時,說明(L,M]區間內的下標都是大於v的,R做爲最後的答案最大隻能是M-1。
上面說過了,當數組爲降序的,使用upper_bound就是返回第一個大於下標,若一開始數組是升續的時候,那麼應該先reverse一下,再用upper_bound返回下標p,則在原數組中的下標爲n-p-1(假設數組有n個元素)。
這裏來介紹一下如何在若是手寫一個last_less()。和upper_bound二分區間[L,R)左閉右開不一樣,last_less_equal()的二分區間爲(L,R]右閉左開。
一樣的,咱們定義(L,R]內的下標都大於等於v,咱們假設R爲當前區間的答案,L爲當前區間的實際答案(由於L是最後一個小於v的下標),咱們每次二分的其實是爲了讓L和R不斷靠近,因此當L==R的時候,咱們假設的答案等於實際的答案,那麼就結束循環了,返回答案L。
#include<bits/stdc++.h> using namespace std; int main(){ int a[100]={1,2,3,3,4,5,5,6,8,9,22},v; while(cin>>v){ int L=-1,R=10; while(L<R){ int M=(L+R+1)/2; if(a[M]<v) L=M; else R=M-1; } cout<<L<<endl; } return 0; }
注:1.當a[M]<n時,因爲L是最後一個小於v下標,那麼L最小隻能是M。
2.當a[M]>=n時,說明(L,M]區間內的下標都是大於等於v的,R做爲最後的答案最大隻能是M-1。
咱們發現lower_bound()和upper_bound()的M=(L+R)/2,而last_less()和last_less_equal()的M=(L+R+1)/2,(L+R)/2和(L+R+1)/2的區別在於前者是向下取整,後者是向上取整,這和咱們定義L或者R是實際的答案有關。