二分查詢寫起來真的簡單嗎?未必!
嗯,其實最簡單的二分查詢寫起來還是挺簡單的,稍微注意下可能出錯的地方即可。用迴圈還是遞迴都可以,我這裡先寫一個
你們也可以搜一下有沒有更好的寫法
/**
* 二分查詢
* @param a 源陣列 注意這個陣列的值一定是經過排序的有序陣列
* @param n 陣列大小
* @param key 想要查詢位置的值
* @return
*/
public int search(int[] a, int n, int key) {
int low = 0;
int high = n - 1;
while (low <= high) {
//這個地方 計算mid的值 有可能會發生 越界異常的,low+high 非常有可能超過int的最大值限制
//所以這邊可以優化成low+(high-low)/2 甚至用到位運算low+((high-low)>>1)
int mid = (low + high) / 2;
if (a[mid] == key) {
return mid;
} else if (a[mid] < key) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
複製程式碼
但是注意了,這裡只是最簡單的二分查詢,實際生產中,我們可能面對的是:
找出第一個值等於key的元素,或者找出最後一個值等於key的元素,甚至還有找出第一個<= 或者>=的元素位。
這種二分查詢寫起來就比較麻煩而且很容易出bug了,有興趣的同學可以去leetcode上寫一下玩一玩。
真正生產環境中二分查詢還是用在 這種近似查詢的地方比較多,真正意義上的 準確查詢,我們還是用hashmap,二叉樹來
做得比較多,因為這2種做準確查詢有額外優勢(雖然這2個需要額外記憶體空間)
二分查詢啥時候用比較合適?
首先要注意的是,二分查詢速度還是挺快的,千萬不要以為他寫起來簡單就認為他不快。。這和寫起來簡單跑起來十分緩慢的
氣泡排序是兩碼事。畢竟是O(logn)的速度,就算你有2的32次方大概40多億的資料量要查一個資料,最多也就32次就可以查完。
但是二分查詢仍舊有自己的侷限性:
-
二分查詢需要資料來源是陣列 假設我們二分查詢用在連結串列為資料結構的地方,可以想一下,連結串列的隨機訪問長度時間
複雜度是O(n)。 我們可以還原一下這個場景:假設用二分查詢在連結串列上,那麼第一次找到這個mid的位置 要移動n/2次,第二次找到mid的位置要移動n/4,以此類推n/8,n/16,可以看出來這個尋找
mid的操作時間複雜度就是O(n),再想想其他操作,這個實際的執行時間肯定比順序查詢要慢了 ,所以二分查詢不可以用在連結串列上,只能用在陣列上 -
二分查詢必須要求資料來源是有序的,且資料來源最好是靜態的,動態的資料來源多數場景不使用二分查詢。
因為二分查詢必須要求資料來源是有序的,所以如果我們資料來源不是有序資料,就必須先進行排序才能查詢。排序當然也
是耗時的,最快也要O(nlogn),所以如果你要查詢的資料來源頻繁的插入刪除,需要頻繁的重新排序的話,用二分查詢
就很慢了。 -
資料量太多就別用二分查詢了,資料量太小也別用,特殊情況最好用。 前面說到二分查詢要用陣列來做
資料結構,如果你這裡資料量太大,那麼你需要的記憶體空間就太大了,比方說你剩餘記憶體是4gb,你的資料有3.8gb,
那麼大概率這裡你用二分查詢要失敗,因為陣列要求的是連續記憶體空間,我們剩餘記憶體有4gb,可不代表著4個gb的剩餘空間是連續的。
資料量太小為啥也不用呢,其實你要用也不是不可以,主要是資料量太少,二分查詢優勢不明顯,寫起來還麻煩,省事的話
資料量小就用順序查詢吧。有一種情況特殊,比如我們的二分查詢的比較操作比較耗時的話就最好用二分查詢了,也就是
說單次比較操作耗時的查詢(比方說長度很長超過500左右的大字串比較),最好用二分查詢,因為二分查詢可以大幅減少比較的次數。
本寶寶就是頭硬,非要在連結串列上實現二分查詢怎麼辦,而且還不能慢?
計算機界有兩句名言。1.電腦科學領域的任何問題都可以通過增加一個間接的中間層來解決。2.如果時間不夠就用空間來換,
如果空間不夠,就用時間來換。
所以,如果想在連結串列上實現二分查詢有序集合還不能慢的話,用空間換時間就是一個好方法。“跳錶” 就是一個很有效的解決方案。
簡單來說所謂跳錶就是對一個連結串列 建立n個索引層,然後查詢的時候 通過索引層來找即可。我畫了個草圖大家可以體會下:
很好理解吧,其實redis的有序集合也用的這個。只不過跳錶這個解決方案出現的比較晚,很多sdk都沒有跳錶的實現,但是
紅黑樹的實現很多,所以跳錶知道的人不多。因為索引的存在,在區間查詢的時候跳錶甚至比紅黑樹還要快一些。
跳錶的具體實現大家可以自行百度學習,比如這個連結就不錯
這裡只做開拓視野,讓大家知道有這麼回事即可。有興趣的同學可以自己寫寫看,跳錶的插入刪除和索引更新的策略都是蠻有意思
的東西。