STL中與二分查詢相關的4個函式
二分查詢的原理非常簡單,但寫出的程式碼中很容易含有很多Bug,二分查詢一文中講解過如何實現不同型別的二分查詢,但是否一定要自己去實現二分查詢呢?答案顯然是否定的,本文將講解STL中與二分查詢有關函式的具體使用方法及其實現原理。
函式使用
STL中與二分查詢相關的函式有4個,分別是lower_bound, upper_bound, equal_range和binary_search,下面通過一個簡單的例子說明各個函式的使用方法。
其中每個函式實現的功能如下:
- binary_search:查詢某個元素是否出現。
- lower_bound:查詢第一個大於或等於某個元素的位置。
- upper_bound:查詢第一個大於某個元素的位置。
- equal_range:查詢某個元素出現的起止位置。注意,終止位置為最後一次出現的位置加一。
其中lower_bound和upper_bound的功能可能比較彆扭,可以這樣看:對於已排序的陣列1 2 2 2 3 4,元素2出現了3次,第一次出現的位置為1,最後一次的位置為3,lower_bound即為這個第一次出現的位置,而upper_bound本意是要標誌最後一次出現的位置,但STL中習慣使用左閉右開的區間,於是upper_bound返回的結果應該為最後一次出現的位置的下一個位置。當查詢的元素一次都未出現時,二者返回的結果都是第一個大於該元素的位置。
函式實現
下面我們具體看每個函式的實現細節。我們這裡使用的仍然是SGI 版本的STL,具體版本為v3.3。
lower_bound
lower_bound查詢第一個大於或等於某個元素val的位置,根據二分查詢,首先找到中間位置的元素mid,然後,
- 如果mid < val,顯然mid本身及其左邊的元素都不可能是答案了,則把first置為 mid + 1。
- 如果mid >= val,答案可能是mid本身或者mid左邊的元素,則把last置為mid。
基本思想就是這樣,但STL實現中使用的是迭代器,而迭代器不一定能隨機訪問,於是它需要使用distance函式計算迭代器間的距離,使用advance函式或者自增、自減運算實現迭代器的前進和後退。另外STL實現中沒有在迴圈中使用first和last,而是使用了等價的first和len,這樣可以方便非隨機訪問的迭代器求取中間值。
STL的實現中lower_bound函式本身只是進行了一系列的型別安全性檢查,真正的工作放到了__lower_bound函式中進行。使用額外輔助函式的另一個目的是在輔助函式中新增模板型別_Distance,可以根據模板引數推導機制得到迭代器的距離型別,從而利用這個型別定義變數。
另外,STL的實現有兩個版本,其中一個使用預設的比較函式<,第二個可以自定義比較方法comp,這裡為了簡潔,只提供了第一個版本的實現。本質上第二個版本實現中也只是把使用第一個版本中<進行比較的地方換成呼叫comp方法進行比較,並沒有實質上的區別。以下的每個函式都是如此,不再重複。
下面是具體的實現程式碼。
upper_bound
upper_bound的實現思路跟上面lower_bound類似,只是上面lower_bound是找大於或等於的,這裡是找大於的。得到mid位置的元素後,
- 如果mid <= val,顯然mid本身及其左邊的元素都不可能是答案了,則把first置為 mid + 1。
- 如果mid > val,則答案可能是mid本身或者mid左邊的元素,把last置為mid。
下面是程式碼實現。
equal_range
equal_range查詢某個元素出現的起止位置,可以通過以上的lower_bound和upper_bound函式實現。STL中也正是通過這兩個函式實現,不過呼叫之前先進行進行普通二分查詢找到第一個出現的位置mid後,
- 在[first, mid)中呼叫lower_bound查詢開始位置。注意,如果該區間沒有找到的話,lower_bound會返回最後的位置mid,而mid此時正是開始位置。
- 在[mid+1, last)中呼叫lower_bound查詢結束位置,這裡的last即為first + len。
具體的程式碼實現如下。
binary_search
binary_search查詢某個元素是否出現,是最普通的二分查詢。STL的實現中使用了lower_bound查詢第一個大於或等於該元素的位置,如果該位置的元素同時也是小於或等於目標元素的,則說明該位置的元素確實等於目標元素,查詢成功。否則查詢失敗。