演算法 - 查詢

qwer1030274531發表於2021-07-31

在演算法中用的比較多的查詢演算法是二分查詢,在這裡梳理一下整數二分和浮點數二分的思路。

將來複習資料結構的話也會在這裡補充。


二分查詢

(一)是什麼

二分查詢(折半查詢:Binary Search)的查詢過程:先確定待查記錄所在的範圍(區間),然後逐步縮小範圍直到找到或找不到該記錄為止。

注:

就算是找不到這個記錄最終也是可以停下的,只不過停下的位置對應的值並不是想要的。


(二)優勢

時間複雜度低:O(logn)

推導:

資料大小為n,每次查詢後資料都會減半,最壞情況為直到區間數為1個時才停止,即:n * (1/2)的x次方 = 1

解得x = logn


(三)兩個板子演算法



首先必須清楚一件事,二分演算法解決的本質是:尋找某一性質的邊界,給出的兩個板子就是找到性質1的右邊界(箭頭1)和性質2的左邊界(箭頭2)。


1.確定性質1的邊界——找箭頭1

mid = l + r + 1 >> 1

if(check(mid)): //判斷是否滿足該性質


Ture

mid在箭頭1的左邊,那要找到箭頭的話需要改變區間:l = mid,使[l, r] -> [mid, r] (包含mid,因為mid也滿足性質。下同理)。

False

mid在箭頭1的右邊,需要:r = mid - 1, 使[l, r] -> [l, mid -1]

2.確定性質2的邊界——找箭頭2

mid = l + r >> 1

if(check(mid)): //判斷是否滿足該性質


Ture

r = mid, [l, r] -> [l, mid]

False

l = mid + 1, [l, r] -> [mid + 1, r]

板子如下:


注意:

1.l = mid時mid後面需要+1

2.這兩個板子的返回結果就是兩個邊界

3.圖中的性質1和性質2往往對應的是小於和大於


bool check(int x){/* ... */}

int bsearch_1(int l, int r){

//性質1

while(l < r){

int mid = l + r + 1 >> 1;

if(check(mid)) l = mid;

else r = mid - 1;

}

return r; //此時l和r一樣,返回誰都可以

}


int bsearch_2(int l, int r){

//性質2

while(l < r){

int mid = l + r >> 1;

if(check(mid)) r = mid;

else l = mid + 1

}

return l;

}


AcWing 789. 數的範圍

這道題就是怎麼用了。比如1 2 2 3 3 4問你3的起始位置和終止位置。(從0開始)

既然是有序的,那很輕易可以看出1 2 2 是<3的,4>3,我們可以將問題轉化為尋找 <=3 右邊界和 >=3 的左邊界。

<=3 的右邊界是位置4,>=3 的左邊界是位置3, 很明顯不是我們想要的答案。那我們直接顛倒順序,尋找 >=3 的左邊界 和 <=3 的右邊界就可以解決問題。

如果尋找不到,那麼邊界位置的值一定不是尋找的值,一個if語句就可以解決。


#include<bits/stdc++.h>


using namespace std;


const int N = 100010;


int a[N];

int n, q;


int main(){

    scanf("%d%d", &n, &q);

    for(int i = 0; i < n; i ++) scanf("%d", &a[i]);


    while(q --){

        int k;

        scanf("%d", &k);


        //尋找k的左邊界

        //>=k的左邊界,滿足性質2

        int l = 0, r = n - 1;

        while(l < r){

            int mid = (l + r) >> 1;


            if(a[mid] >= k) r = mid;

            else l = mid + 1;

        }


        if(a[l] != k) cout << "-1 -1"<<endl;

        else{

            printf("%d ", l);


            //尋找k的右邊界

            //<=k的右邊界,滿足性質1

            l = 0, r = n - 1;

            while(l < r){

                int mid = (l + r + 1) >> 1;


                if(a[mid] <= k) l = mid; // l = mid 前面的mid應該+1

                else r = mid - 1;

            }


            printf("%d\n", r);

        }

    }

    return 0;

}


最常用的就是上面的整數二分板子了,浮點數二分更簡單,沒有+1-1的那些事兒。板子如下:


const double eps = 1e-6 ;一般定義的比題給的小兩位

double bsearch_3(double l, double r){

while(r - l > eps){

double mid = l + r >> 1;

if(check(mid)) r = mid;  //結合題

else l = mid;

}

return l;

}

eg.求三次方根


#include<bits/stdc++.h>

using namespace std;


const double eps = 1e-8 ;//經驗之談,eps比題目要求的多兩位


int main(){

    double n;

    scanf("%lf", &n);

    double l = -10000, r = 10000;


    while(r - l > eps){

        double mid = (r + l)/2;

        if(mid * mid * mid >= n) r = mid;

        else l = mid;

    }

    printf("%lf", l); //double型別輸出預設是6位,符合題目要求

    

    return 0;

    

}


(四)哪些情況使用

這裡談論的就不僅僅是演算法了,還有資料結構的一些東西。


儲存的資料結構應該是可以實現隨機訪問的陣列,而不是連結串列。(連結串列不支援隨機訪問)

陣列內的資料必須有序

更適合處理靜態資料(沒有頻繁的資料插入、刪除操作)

太小的不適合,因為順序查詢就可以解決

太大的也不適合,因為二分底層依賴的就是陣列,而陣列在記憶體中就是一段連續的空間,你需要1G的空間,即使記憶體剩下2G可能也不夠用,因為剩餘的2G可能是離散分配後的結果,並沒有1G的連續空間。

二分依賴的只有陣列,所以大部分情況還是可以解決的。同時二分可以解決的雜湊表、二叉樹也都可以解決。這些以後再補充,演算法這裡最常用的是二分。


————————————————

版權宣告:本文為CSDN博主「御用廚師」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。

原文連結:https://blog.csdn.net/qq_45520647/article/details/119176850


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30239065/viewspace-2784442/,如需轉載,請註明出處,否則將追究法律責任。

相關文章