【IT之家評測室】螢石智慧家居攝像機 2K 超感知版體驗:家用場景,一機滿足
二分查詢
首先,我們丟擲一個經典的問題:如何在一個嚴格遞增序列A中找出給定的數x。
最直接的辦法就是對序列進行現行掃描所有元素,如果找到x則成功,如果沒找到就失敗。
這種順序查詢的時間複雜度為O(n),當查詢資料較小時,是個很好的選擇,但資料量太大就不行了。
由此,我們可以通過二分查詢來縮短時間。
一般的二分做法(嚴格遞增遞減序列)
明確一點,二分查詢是基於有序序列的查詢演算法,這裡僅以嚴格遞增序列為例子,對於其他有序序列做法類似。
二分查詢的高效在於每一步二分都能夠去除當前區間一半的元素,所以時間複雜度為\(O(log n)\)。
我們先設[left,right]為序列A的整個下標區間,然後不斷二分查詢。
#include <iostream> #include <cstdio> using namespace std; // A為嚴格遞增序列,left為二分下界,right為二分上界,x為查詢的數 // 二分割槽間為[left,right],傳入[0,n-1] int binarySearch(int A[],int left,int right,int x){ int mid; while (left <= right){ // 注意<=,取=時還要再判斷是否找到x mid = (left+right)/2; if(A[mid] == x) return mid;// 找到x,返回下標 else if(A[mid] > x) right = mid-1; else left = mid+1; } return -1;// 查詢失敗,返回-1 } int main(){ const int n = 10; int A[n] = {0,3,5,7,9,11,13,15,16,19}; cout << binarySearch(A,0,n-1,13) << ' ' << binarySearch(A,0,n-1,8) << endl; return 0; }
這裡的迴圈條件是left <= right
,當left>right
時可以作為元素x不存在的判定條件。
注意:mid= (left+right)/2
中的left+right有可能超出int範圍而溢位,所以一般用imd=left + (right-left) >> 1
,是等價的。(位運算會稍快一點)
以下所涉及的序列未說明是嚴格遞增遞減序列。
對於遞增(遞減)序列,要在其中找到x(如果有多個)的位置範圍。如果我們能夠求出序列中第一個大於等於x的元素的位置L,以及序列中第一個大於x的元素的位置R,這樣元素x在序列中的存在區間就是[L,R)
。
如何求序列中第一個大於等於x的元素的位置(下界)
如果序列中存在元素x,那麼序列中第一個大於等於x的元素的位置也就是第一個x的位置,下界;
如果序列中不存在元素x,那麼返回值是序列中下標,或者是n。
這裡僅以遞增序列為例子。
注意:這裡的二分割槽間是[0,n],不再是上面的[0,n-1],因為x可能比序列中所有元素都大。
#include <iostream>
#include <cstdio>
using namespace std;
// A為遞增序列,left為二分下界,right為二分上界,x為查詢的數
// 二分割槽間為[left,right],傳入[0,n]
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;
else left = mid+1;
}
return left;// 返回夾出來的位置
}
int main(){
const int n = 10;
int A[n] = {0,3,5,7,7,7,7,15,17,19};
cout << lower_bound(A,0,n,7) << ' ' << lower_bound(A,0,n,20) << endl;
return 0;
}
說明:
當A[mid]>=x
時,說明第一個大於等於x的元素的位置一定在mid處或mid的左側,所以往[left,mid]
查詢。
當A[mid]<x
時,說明第一個大於等於x的元素的位置一定在mid右側,所以往[mid+1,right]
查詢。
由於第一個大於等於x的元素的位置肯定存在,所以當left==right
時,所夾出來的位置就是所求下標,這裡返回right也是一樣的。
如何求序列中第一個大於x的元素的位置(上界往上)
做法類似上一個問題。
#include <iostream>
#include <cstdio>
using namespace std;
// A為遞增序列,left為二分下界,right為二分上界,x為查詢的數
// 二分割槽間為[left,right],傳入[0,n]
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;// 返回夾出來的位置
}
int main(){
const int n = 10;
int A[n] = {0,3,5,7,7,7,7,15,17,19};
cout << lower_bound(A,0,n,7) << ' ' << lower_bound(A,0,n,20) << endl;
return 0;
}
眼力好的同學肯定發現了,這裡只是把上一問中的A[mid]>=x
換成了A[mid]>x
,其他完全一樣。
說明:
當A[mid]>x
時,說明第一個大於x的元素的位置一定在mid處或mid的左側,所以往[left,right]
查詢。
當A[mid]<=x
時,說明第一個大於x的元素的位置一定在mid的右側,所以往[mid+1,right]
查詢。
補充資料
在STL中,C++其實已經幫我們寫好了二分的lower_bound和upper_bound函式。
選擇庫函式或者自己實現都是可以的。