【學習筆記】二分查詢
二分查詢的學習筆記
之前在bilibili看到一個有趣的視訊,關於二分查詢的。戳我看這個有趣的視訊
筆記參考:極客時間|資料結構與演算法之美
1. 二分思想
就如上面視訊中的栗子,猜數字遊戲,如果從頭開始一個一個的猜是非常低效的。
在實際的開發場景中,假設有1w條訂單資料,已經按照訂單的金額從小到大排序,每個定乾的金額不同,最小單位是元。
如果從第一個訂單開始,遍歷這1w條訂單,直到找到目標為止,這種方法在最壞情況下可能要便利完所有資料才能找到。
但是用二分法,每次都通過跟區間中間元素對比,將整個帶查詢的區間縮小為之前的一半,直到查詢的需要的資料或者區間縮小為0。
2. 二分查詢實現(遞迴和非遞迴)
// 非遞迴實現
int bsearch(int a[], int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high)
{
int mid = (low + high) / 2;
if (a[mid] == value)
{
return mid;
}
else if (a[mid] < value)
{
low = mid + 1;
}
else
{
high = mid - 1;
}
}/*while*/
return -1;
}
- 迴圈退出條件
low<=high
- mid的取值
mid=(low+high)/2
在low和high比較大的話兩者之和容易溢位。
改進:low+(high-low)/2
更高效:low+((high-low)>>1)
- low和high的更新
low=mid+1
和high=mid-1
這裡的+1和-1,如果寫成low=mid或high=mid
就可能發生死迴圈。
// 二分查詢的遞迴實現
int bsearch(int a[], int n, int val)
{
return b(a, 0, n - 1, val);
}
int b(int a[], int low, int high, int value)
{
if (low > high) return -1;
int mid = low + ((high - low) >> 1);
if (a[mid] == value)
{
return mid;
}
else if (a[mid] < value)
{
return bsearchInternally(a, mid+1, high, value);
}
else
{
return bsearchInternally(a, low, mid-1, value);
}
}
3. 時間複雜度
二分查詢的被查詢區間縮小速度是一個等比數列,公比q為1/2。
假設資料規模為n,每次查詢後資料規模都會縮小為原來的一半,最壞情況下,直到查詢區間被縮小為空停止。
n/2^k=1
時,k就是總共縮小的次數,每次縮小隻涉及2個數據的比較,經過k次區間縮小操作,時間複雜度為O(k)。k等於以2為底n的對數,解得時間複雜度為O(logn)
。
O(logn)
在某些情況下比O(1)
時間複雜度的演算法高效。O(1)
時忽略掉常熟、係數和低階。當資料規模夠大時,如O(10000)
,此時log(n)
的演算法效率比O(1)
的高。
4. 侷限性
- 二分查詢依賴順序結構
- 二分查詢針對的是有序資料
- 資料量太小不適合二分查詢
比如在10個元素的陣列中查詢一個元素,遍歷會來的更方便一些。而且查詢速度也差不多。
- 資料量太大也不適合二分查詢
為了支援隨機訪問,記憶體空間需要連續。
5. 二分查詢的變體問題
- 查詢第一個值等於給定值的元素
當區間中間值不等於要查詢的元素時,需要繼續查詢。
當區間中間值等於要查詢的元素時,看看中間值的前一個值是否等於要查詢的元素,如果等則更新high=mid-1,否則現在這個元素就是要查詢的元素。
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else if (a[mid] < value) {
low = mid + 1;
} else {
if ((mid == 0) || (a[mid - 1] != value)) return mid;
else high = mid - 1;
}
}
return -1;
}
- 查詢最後一個值等於給定值的元素
跟第一種一樣,不過這次要和mid後面的一個元素進行比較。更新low=mid+1。
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else if (a[mid] < value) {
low = mid + 1;
} else {
if ((mid == n - 1) || (a[mid + 1] != value)) return mid;
else low = mid + 1;
}
}
return -1;
}
- 查詢第一個大於等於給定值的元素
如果mid的值小於要查詢的值,則目標值在[mid+1,high]之間,更新low。
如果mid的值大於要查詢的值,目標值在[low,mid-1]之間,更新high。
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] >= value) {
if ((mid == 0) || (a[mid - 1] < value)) return mid;
else high = mid - 1;
} else {
low = mid + 1;
}
}
return -1;
}
- 查詢最後一個小於等於給定值的元素
要查詢的值大於當前mid中的值,目標值在[low,mid-1]之間,更新high=mid-1。
要查詢的值小於mid+1的值,則要查詢的值在[mid+1,high]之間,更新low=mid+1。
public int bsearch7(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else {
if ((mid == n - 1) || (a[mid + 1] > value)) return mid;
else low = mid + 1;
}
}
return -1;
}