這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。 03 Jun 2013
binary array search的幾種寫法(go)
兒童節那天,對比了python內建的dict和binary array search尋找的效能,dict大幅領先,有些出乎意料:
- dict雖然是O(1),binary search是O(log n)。當n為1M時,log n也僅有20,並且直觀上log n前面的常數不大,理論上應該和O(1)差別不是很大
- 懷疑到python語言,binary array search是由手工python實現,而dict是C實現。python語言比C慢很多
所以,6月2日,用go語言來對比一下,也熟悉一下go語言。 binary search有幾種寫法:
recusive
: 遞迴
3-branch
: 迭代,每次迭代進行2次判斷 ==
,<
(或者>
),三個分支。找到後,立刻退出。最好情況為O(1)
2-branch
: 迭代,每次迭代進行1次判斷 <
(或者>
),二個分支。最好情況也為O(log n)
library
: go標準庫sort.Search
,它是3的通用版本,需要提供一個函數,用來提供>
或<
的依據
2-branch
3的想法比較有意思,它是先計算出對於要尋找的數,如果放入這個已經排好序的數組array,下標是多少。如果在array裡,有重複,返回的是則是第一個的下標。最後再對比一下數組裡面的數,是否正是需要尋找的key
func binary_search2(arr []int, key int) int {lo, hi := 0, len(arr)-1for lo < hi {mid := (lo + hi) / 2if arr[mid] < key {lo = mid + 1} else {hi = mid}}if lo == hi && arr[lo] == key {return lo} else {return -(lo + 1)}}
library
4的做法很好玩,實現search.go:
func Search(n int, f func(int) bool) int
go語言沒有模版,沒有像java那樣的Comparator,沒有運算子多載,還是靜態類型。但這個介面完全不需要這些,並且非常通用(雖有一點效能的損失)
3-branch和recusive
1和2是常規解法了。
// recusivefunc binary_search0(arr []int, key int) int {return binary_search_(arr, 0, len(arr), key)}func binary_search_(arr []int, begin, end, key int) int {if begin > end {return -1}mid := (begin + end) / 2if arr[mid] == key {return mid} else if arr[mid] > key {return binary_search_(arr, begin, mid-1, key)} else {return binary_search_(arr, mid+1, end, key)}}// 3-branchfunc binary_search(arr []int, key int) int {lo, hi := 0, len(arr)-1for lo <= hi {mid := (lo + hi) / 2if arr[mid] == key {return mid} else if arr[mid] < key { // search upper subarraylo = mid + 1} else {hi = mid - 1}}return -(lo + 1)}
效能比較
隨機產生2k到500k個int,進行1500次查詢,紀錄下時間。並以內建的map做為對比
- map依然是最快的,但沒有像python那樣,快得囂張
- go標準庫的很通用,也是最慢的
- 隨著item數量的增多,2-branch相對與3-branch的優勢明顯:得益於雖然可能迭代的次數增多(不找到就返回了),但每次2個比較,3個branch => 1個比較2個branch。JDK裡面java.util.Arrays.binarySearch用的是3-branch版本,可能在數量不是很大(少於2k)有優勢,或有其它考慮
遞迴,看上去還行,但易於編寫,不易出錯。很好
代碼binary_search.go,把結果畫成比較好看的柱狀圖binary_hash_go.py
ps:這個周末除了和朋友聚聚,為女朋友的侄子採購玩具,剩下就是折騰binary search了。