1. 程式人生 > >1024:二分查詢(上)

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位?