1. 程式人生 > 資訊 >【IT之家評測室】螢石智慧家居攝像機 2K 超感知版體驗:家用場景,一機滿足

【IT之家評測室】螢石智慧家居攝像機 2K 超感知版體驗:家用場景,一機滿足

二分查詢是一類很常見的演算法。 本文通過一個經典的問題:“如何在一個嚴格遞增序列A中找出給定的數x”來介紹整數二分。

二分查詢

首先,我們丟擲一個經典的問題:如何在一個嚴格遞增序列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函式。

選擇庫函式或者自己實現都是可以的。