二分查詢演算法詳解

2021-10-23 22:10:16 字數 2964 閱讀 4588

二分查詢針對的是乙個有序的資料集合,查詢思想有點類似分治思想。每次都通過跟區間的中間元素對比,將待查詢的區間縮小為之前的一半,直到找到要查詢的元素,或者區間被縮小為 0。

二分查詢是一種非常高效的查詢演算法,高效到什麼程度呢?我們來分析一下它的時間複雜度。

我們假設資料大小是 n,每次查詢後資料都會縮小為原來的一半,也就是會除以 2。最壞情況下,直到查詢區間被縮小為空,才停止。

可以看出來,這是乙個等比數列。其中 n/2k=1 時,k 的值就是總共縮小的次數。而每一次縮小操作只涉及兩個資料的大小比較,所以,經過了 k 次區間縮小操作,時間複雜度就是 o(k)。通過 n/2k=1,我們可以求得 k=log2n,所以時間複雜度就是 o(logn)。

二分查詢的時間複雜度是 o(logn),查詢資料的效率非常高。不過,並不是什麼情況下都可以用二分查詢,它的應用場景是有很大侷限性的。那什麼情況下適合用二分查詢,什麼情況下不適合呢?

首先,二分查詢依賴的是順序表結構,簡單點說就是陣列。

那二分查詢能否依賴其他資料結構呢?比如鍊錶。答案是不可以的,主要原因是二分查詢演算法需要按照下標隨機訪問元素。陣列按照下標隨機訪問資料的時間複雜度是 o(1),而鍊錶隨機訪問的時間複雜度是 o(n)。所以,如果資料使用鍊錶儲存,二分查詢的時間複雜就會變得很高。

二分查詢只能用在資料是通過順序表來儲存的資料結構上。如果你的資料是通過其他資料結構儲存的,則無法應用二分查詢。

其次,二分查詢針對的是有序資料。

二分查詢對這一點的要求比較苛刻,資料必須是有序的。如果資料沒有序,我們需要先排序。前面章節裡我們講到,排序的時間複雜度最低是 o(nlogn)。所以,如果我們針對的是一組靜態的資料,沒有頻繁地插入、刪除,我們可以進行一次排序,多次二分查詢。這樣排序的成本可被均攤,二分查詢的邊際成本就會比較低。

但是,如果我們的資料集合有頻繁的插入和刪除操作,要想用二分查詢,要麼每次插入、刪除操作之後保證資料仍然有序,要麼在每次二分查詢之前都先進行排序。針對這種動態資料集合,無論哪種方法,維護有序的成本都是很高的。

所以,二分查詢只能用在插入、刪除操作不頻繁,一次排序多次查詢的場景中。針對動態變化的資料集合,二分查詢將不再適用。那針對動態資料集合,如何在其中快速查詢某個資料呢?別急,等到二叉樹那一節我會詳細講。

再次,資料量太小不適合二分查詢。

如果要處理的資料量很小,完全沒有必要用二分查詢,順序遍歷就足夠了。比如我們在乙個大小為 10 的陣列中查詢乙個元素,不管用二分查詢還是順序遍歷,查詢速度都差不多。只有資料量比較大的時候,二分查詢的優勢才會比較明顯。

不過,這裡有乙個例外。如果資料之間的比較操作非常耗時,不管資料量大小,我都推薦使用二分查詢。比如,陣列中儲存的都是長度超過 300 的字串,如此長的兩個字串之間比對大小,就會非常耗時。我們需要盡可能地減少比較次數,而比較次數的減少會大大提高效能,這個時候二分查詢就比順序遍歷更有優勢。

最後,資料量太大也不適合二分查詢。

二分查詢的底層需要依賴陣列這種資料結構,而陣列為了支援隨機訪問的特性,要求記憶體空間連續,對記憶體的要求比較苛刻。比如,我們有 1gb 大小的資料,如果希望用陣列來儲存,那就需要 1gb 的連續記憶體空間。

注意這裡的「連續」二字,也就是說,即便有 2gb 的記憶體空間剩餘,但是如果這剩餘的 2gb 記憶體空間都是零散的,沒有連續的 1gb 大小的記憶體空間,那照樣無法申請乙個 1gb 大小的陣列。而我們的二分查詢是作用在陣列這種資料結構之上的,所以太大的資料用陣列儲存就比較吃力了,也就不能用二分查詢了。

尋找第乙個值等於給定值的元素

public int bsearchv1(int arr, int n, int value)  else if (arr[mid] < value)  else  else 

}}

我們分析一下第乙個案例的**:

arr[mid]跟要查詢的 value 的大小關係有三種情況:大於、小於、等於。對於 arr[mid]>value 的情況,我們需要更新 high= mid-1;對於 a[mid]如果我們查詢的是任意乙個值等於給定值的元素,當 a[mid]等於要查詢的值時,arr[mid]就是我們要找的元素。但是,如果我們求解的是第乙個值等於給定值的元素,當 a[mid]等於要查詢的值時,我們就需要確認一下這個 a[mid]是不是第乙個值等於給定值的元素。

如果 mid 等於 0,那這個元素已經是陣列的第乙個元素,那它肯定是我們要找的;如果 mid 不等於 0,但 arr[mid]的前乙個元素 arr[mid-1]不等於 value,那也說明 a[mid]就是我們要找的第乙個值等於給定值的元素。

如果經過檢查之後發現 arr[mid]前面的乙個元素 arr[mid-1]也等於 value,那說明此時的 arr[mid]肯定不是我們要查詢的第乙個值等於給定值的元素。那我們就更新 high=mid-1,因為要找的元素肯定出現在[low, mid-1]之間。

尋找最後乙個值等於給定值的元素

public int bsearchv2(int arr, int n, int value)  else if (arr[mid] < value)  else  else }}

return -1;

}

尋找第乙個大於等於給定值的元素

//尋找第乙個大於等於給定值的元素

public int bsearchv3(int arr, int n, int value) else else }}

return -1;

}

尋找最後乙個小於等於給定值的元素

public int bsearchv4(int arr, int n, int value)  else  else }}

return -1;

}

詳解二分查詢演算法

二分查詢是通過將遞增序列不斷減半的方式尋找目標值的下標。其看似簡單,但往往存在一些細節被忽略,即while迴圈判斷條件和區間右邊界的取值方式。另外,我們往往只知二分查詢搜尋目標值的功能,而忽略了二分查詢的另外乙個功能,即尋找最大的小於目標值的元素和最小的大於目標值的元素。目錄 一 二分查詢演算法的基...

二分查詢演算法詳解

最近刷了很多二分查詢相關的題目,這裡將近期的收穫做乙個總結,包括二分查詢的變形問題。如果能掌握,我相信以後基本上二分查詢相關的問題對你來說,都不是問題。二分查詢是啥我想不用過多的說明。我們都知道二分查詢的時間複雜程度是o logn o logn 查詢速度有多快呢?我們來分析一下。我們假設資料大小是 ...

二分查詢詳解

演算法概括 二分查詢又稱折半查詢,優點是比較次數少,查詢速度快,平均效能好 其缺點是要求待查表為有序表,且插入刪除困難。因此,折半查詢方法適用於不經常變動而查詢頻繁的有序列表。首先,假設表中元素是按公升序排列,將表中間位置記錄的關鍵字與查詢關鍵字比較,如果兩者相等,則查詢成功 否則利用中間位置記錄將...