Python 二分查詢與 bisect 模組

發表於2016-10-13

Python 的列表(list)內部實現是一個陣列,也就是一個線性表。在列表中查詢元素可以使用 list.index() 方法,其時間複雜度為O(n)。對於大資料量,則可以用二分查詢進行優化。二分查詢要求物件必須有序,其基本原理如下:

  • 1.從陣列的中間元素開始,如果中間元素正好是要查詢的元素,則搜素過程結束;
  • 2.如果某一特定元素大於或者小於中間元素,則在陣列大於或小於中間元素的那一半中查詢,而且跟開始一樣從中間元素開始比較。
  • 3.如果在某一步驟陣列為空,則代表找不到。

二分查詢也成為折半查詢,演算法每一次比較都使搜尋範圍縮小一半, 其時間複雜度為 O(logn)。

我們分別用遞迴和迴圈來實現二分查詢:

接著對這兩種實現進行一下效能測試:

執行結果如下:

可以看出迴圈方式比遞迴效率高。

Python 有一個 bisect 模組,用於維護有序列表。bisect 模組實現了一個演算法用於插入元素到有序列表。在一些情況下,這比反覆排序列表或構造一個大的列表再排序的效率更高。Bisect 是二分法的意思,這裡使用二分法來排序,它會將一個元素插入到一個有序列表的合適位置,這使得不需要每次呼叫 sort 的方式維護有序列表。

下面是一個簡單的使用示例:

輸出結果:

Bisect模組提供的函式有:

  • bisect.bisect_left(a,x, lo=0, hi=len(a)) :

查詢在有序列表 a 中插入 x 的index。lo 和 hi 用於指定列表的區間,預設是使用整個列表。如果 x 已經存在,在其左邊插入。返回值為 index。

  • bisect.bisect_right(a,x, lo=0, hi=len(a))
  • bisect.bisect(a, x,lo=0, hi=len(a)) :

這2個函式和 bisect_left 類似,但如果 x 已經存在,在其右邊插入。

  • bisect.insort_left(a,x, lo=0, hi=len(a)) :

在有序列表 a 中插入 x。和 a.insert(bisect.bisect_left(a,x, lo, hi), x) 的效果相同。

  • bisect.insort_right(a,x, lo=0, hi=len(a))
  • bisect.insort(a, x,lo=0, hi=len(a)) :

和 insort_left 類似,但如果 x 已經存在,在其右邊插入。

Bisect 模組提供的函式可以分兩類: bisect* 只用於查詢 index, 不進行實際的插入;而 insort* 則用於實際插入。該模組比較典型的應用是計算分數等級:

執行結果:

同樣,我們可以用 bisect 模組實現二分查詢:

我們再來測試一下它與遞迴和迴圈實現的二分查詢的效能:

可以看到其比迴圈實現略快,比遞迴實現差不多要快一半。

Python 著名的資料處理庫 numpy 也有一個用於二分查詢的函式 numpy.searchsorted, 用法與 bisect 基本相同,只不過如果要右邊插入時,需要設定引數 side='right',例如:

那麼,我們再來比較一下效能:

可以發現 numpy.searchsorted 效率是很低的,跟 bisect 根本不在一個數量級上。因此 searchsorted 不適合用於搜尋普通的陣列,但是它用來搜尋 numpy.ndarray 是相當快的:

numpy.searchsorted 可以同時搜尋多個值:

相關文章