由於常年二分寫成死循環,所以是時候有必要總結一下二分搜索了,這里聲明一下本人的二分風格是左閉右開也就是[L,R)。
這里就不解釋什么是二分搜索了,這里將會介紹4種二分搜索,和二分搜索常用來解決的最小值最大化或者最大值最小化的問題,我們都知道使用二分的最基本條件是,我們二分的序列需要有單調性,這里的序列是廣義性如:1.一個排好序的數組; 2.一個區間[L,R);3.其他(暫時想不到)。所以下面介紹的時候會用v來代表我們二分的目標,用第一個大於v,第一個大於等於v,最后一個小於v,最后一個小於等於v來描述,這里可以看到我即將要介紹的4種二分搜索。
1.第一個大於等於v
這就是我們常說的lower_bound()了,這是系統里面自帶的庫函數,在數組或者一個vector容器中二分的時候,也就是不是必須手寫二分的時候首推使用這個,優點代碼少,穩定(建議少裝逼,動不動手寫二分)。這里我們來介紹lower_bound()的使用方式。
首先是這個函數原型:
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的數的下標 }
當對結構體數組進行二分搜索時(我們可以在這里繼續輸入上面代碼的初始化的數據)
#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的下標。
學會了如何使用庫函數,現在我們來學習一下如何手寫一個lower_bound(),我們知道二分有一個左邊界L和右邊界R,我們定義[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
2.第一個大於v
這就是我們常說的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
3.最后一個小於等於v
上面說過了,當數組為降序的,使用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。
4.最后一個小於v
上面說過了,當數組為降序的,使用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是實際的答案有關。
