1024:二分查詢(上)
目錄
一、什麼是二分查詢?
二分查詢針對的是一個有序的資料集合,每次通過跟區間中間的元素對比,將待查詢的區間縮小為之前的一半,直到找到要查詢的元素,或者區間縮小為0。
二、時間複雜度分析?
1.時間複雜度
假設資料大小是n,每次查詢後資料都會縮小為原來的一半,最壞的情況下,直到查詢區間被縮小為空,才停止。所以,每次查詢的資料大小是:n,n/2,n/4,…,n/(2^k),…,這是一個等比數列。當n/(2^k)=1時,k的值就是總共縮小的次數,也是查詢的總次數。而每次縮小操作只涉及兩個資料的大小比較,所以,經過k次區間縮小操作,時間複雜度就是O(k)。通過n/(2^k)=1,可求得k=log2n,所以時間複雜度是O(logn)。
2.認識O(logn)
①這是一種極其高效的時間複雜度,有時甚至比O(1)的演算法還要高效。為什麼?
②因為logn是一個非常“恐怖“的數量級,即便n非常大,對應的logn也很小。比如n等於2的32次方,也就是42億,而logn才32。
③由此可見,O(logn)有時就是比O(1000),O(10000)快很多。
三、如何實現二分查詢?
1.迴圈實現
程式碼實現:
public int binarySearch1(int[] a, int val){ int start = 0; int end = a.length - 1; while(start <= end){ int mid = start + (end - start) / 2; if(a[mid] > val) end = mid - 1; else if(a[mid] < val) start = mid + 1; else return mid; } return -1; }
注意事項:
①迴圈退出條件是:start<=end,而不是start<end。
②mid的取值,使用mid=start + (end - start) / 2,而不用mid=(start + end)/2,因為如果start和end比較大的話,求和可能會發生int型別的值超出最大範圍。為了把效能優化到極致,可以將除以2轉換成位運算,即start + ((end - start) >> 1),因為相比除法運算來說,計算機處理位運算要快得多。
③start和end的更新:start = mid - 1,end = mid + 1,若直接寫成start = mid,end=mid,就可能會發生死迴圈。
2.遞迴實現
public int binarySearch(int[] a, int val){
return bSear(a, val, 0, a.length-1);
}
private int bSear(int[] a, int val, int start, int end) {
if(start > end) return -1;
int mid = start + (end - start) / 2;
if(a[mid] == val) return mid;
else if(a[mid] > val) end = mid - 1;
else start = mid + 1;
return bSear(a, val, start, end);
}
四、使用條件(應用場景的侷限性)
1.二分查詢依賴的是順序表結構,即陣列。
2.二分查詢針對的是有序資料,因此只能用在插入、刪除操作不頻繁,一次排序多次查詢的場景中。
3.資料量太小不適合二分查詢,與直接遍歷相比效率提升不明顯。但有一個例外,就是資料之間的比較操作非常費時,比如陣列中儲存的都是長度超過300的字串,那這是還是儘量減少比較操作使用二分查詢吧。
4.資料量太大也不是適合用二分查詢,因為陣列需要連續的空間,若資料量太大,往往找不到儲存如此大規模資料的連續記憶體空間。
五、思考
1.如何在1000萬個整數中快速查詢某個整數?
①1000萬個整數佔用儲存空間為40MB,佔用空間不大,所以可以全部載入到記憶體中進行處理;
②用一個1000萬個元素的陣列儲存,然後使用快排進行升序排序,時間複雜度為O(nlogn)
③在有序陣列中使用二分查詢演算法進行查詢,時間複雜度為O(logn)
2.如何程式設計實現“求一個數的平方根”?要求精確到小數點後6位?