八、二分查詢(Binary Search)
一、概述
二分查詢(Binary Search,也稱折半查詢)——針對有序資料集合的查詢演算法
1、基本思想
類似分治思想,每次都通過跟區間的中間元素進行對比,將代查詢的區間縮小為之前的一半,直到找到要查詢的元素,或者區間被縮小為0(不存在該元素)。
2、時間複雜度分析——O(logn)
不妨假設資料量為n,每次查詢後資料量均為原來的1/2,最壞的情況下,直到查詢區間被縮小為空,才停止。設經過k次區間縮小操作區間縮小到1,可得 k=log2n 時間複雜度為O(k),也就是O(logn)。
O(1) 常量級時間複雜度的演算法可能還沒有O(logn)的演算法執行效率高。
二、簡單實現
二分查詢最簡單的情況:有序陣列中不存在重複元素
1、遞迴方法實現
#include<iostream>
using namespace std;
int BinarySearchRecursive(int *array, int low, int high, int key)
{
if(low > high)
return -1;
// int mid = (low + high) / 2; 若low和high比較大,兩者之和就可能溢位
int mid = low + (high - low) / 2;
// 若效能有要求,可將除以2操作轉化為位運算,以提升效率
// int mid = low + ((high - low)>>1)
if(array[mid] == key)
return mid;
else if(array[mid] < key)
return BinarySearchRecursive(array, mid+1, high, key);
else
return BinarySearchRecursive(array, low, mid-1, key);
}
int main()
{
int array[10];
for(int i = 0; i < 10; i++)
array[i] = i;
cout<< "Recursive:"<<endl;
cout<<"postion:"<<BinarySearchRecursive(array,0,9,4)<<endl;
return 0;
}
2、非遞迴方法
int BinarySearch(int *array, int aSzie, int key)
{
if(array == NULL || aSize == 0)
return -1;
int low = 0;
int high = aSize - 1;
int mid = 0;
while(low <= high)
{
mid = low + (high - low) / 2;
if(array[mid] == key)
return mid;
else if(array[mid] < key)
low = mid + 1;
else
high = mid - 1;
}
return -1;
}
3、應用場景的侷限性
適用於:插入、刪除操作都不頻繁,一次排序多次查詢且資料量較大的場景。
-
二分查詢依賴的是順序表資料結構,也就是陣列(可以按照下標隨機訪問元素)。
-
二分查詢的資料是有序的:排序演算法時間複雜度最低為O(nlogn),若動態變化的資料集合,不再適用。
-
適合資料量較大的場景,而非特別大。因為陣列為了支援隨機訪問的特性,要求記憶體空間連續。
特例:若資料之間的比較操作特別耗時,不管資料量大小,都推薦使用二分查詢。同on個過儘可能較少比較次數來提升效能。
三、應用例項
1、例項
假設我們有 1000 萬個整數資料,每個資料佔 8 個位元組,如何設計資料結構和演算法,快速判斷某個整數是否出現在這 1000 萬資料中? 我們希望這個功能不要佔用太多的記憶體空間,最多不要超過 100MB,你會怎麼做呢?
2、分析
由於每個資料佔 8 個位元組,記憶體佔用差不多為80M < 100M,符合記憶體限制。
==》先從小到大排序,然後利用二分查詢演算法找資料。
注意:散列表、二叉樹這些支援快速查詢的動態資料結構。但是,在該情況卻不行的。雖然在大部分情況下,用二分查詢可以解決的問題,用散列表、二叉樹都可以解決。但是都會需要比較多的額外的記憶體空間。所以,如果用散列表或者二叉樹來儲存這
1000 萬的資料,用 100MB 的記憶體肯定是存不下的。而二分查詢底層依賴的是陣列,除了資料本身之外,不需要額外儲存其他資訊,是最省記憶體空間的儲存方式,所以剛好能在限定的記憶體大小下解決這個問題。
四、二分查詢的進級(變形問題)
前提:資料都是從小到大排列的,且資料中存在重複的資料。
1、常見的變形問題
- 查詢第一個值等於給定值的元素
- 查詢最後一個值等於給定值的元素
- 查詢第一個大於等於給定值的元素
- 查詢最後一個小於等於給定值的元素
2、查詢第一個值等於給定值的元素
(1)方法一
int bsearch1(int *array, int aSize, int key)
{
int low = 0;
int high = aSize - 1;
while (low <= high) {
int mid = low + ((high - low)/2);
if(array[mid] > key)
high = mid - 1;
else if(array[mid] < key)
low = mid + 1;
else
// 當array[mid]=key時,
// 需要確認一下這個 array[mid] 是不是第一個值等於給定值的元素
{
// 若mid等於0,說明該元素為第一個元素;
// array[mid]的前一個元素array[mid-1]不等於key,說明該元素為第一個元素;
if((mid == 0)||(array[mid-1]!=key))
return mid;
// 否則更新區間
else
high = mid - 1;
}
}
return -1;
}
(2)方法二
int bsearch2(int *array, int aSize, int key)
{
int low = 0;
int high = aSize - 1;
while (low <= high)
{
int mid = low + ((high - low) / 2);
if(array[mid] >= key)
{
high = mid - 1;
}
else
{
low = mid + 1;
}
}
if(array[low] == key)
return low;
else
return -1;
}
3、查詢最後一個值等於給定值的元素
int bSearch(int *array,int aSize,int key)
{
int low = 0;
int high = aSize - 1;
while(low <= high)
{
int mid = low + ((high - low) / 2);
if (array[mid] >= key)
high = mid - 1;
else if (array[mid < key]) {
low = mid + 1;
}
else
{
if((mid == aSize - 1)||(array[mid+1] != key))
return mid;
else
low = mid + 1;
}
}
return -1;
}
4、查詢第一個大於等於給定值的元素
int bSearch(int *array,int aSize,int key)
{
int low = 0;
int high = aSize - 1;
while(low <= high)
{
int mid = low + ((high - low) / 2);
if (array[mid] >= key)
{
if((mid == 0)||(array[mid-1] < key))
return mid;
else
high = mid - 1;
}
else
{
low = mid + 1;
}
}
return -1;
}
5、查詢最後一個小於等於給定值的元素
int bSearch(int *array,int aSize,int key)
{
int low = 0;
int high = aSize - 1;
while(low <= high)
{
int mid = low + ((high - low) / 2);
if (array[mid] > key)
{
high = mid - 1;
}
else
{
if((mid == aSize - 1)||(array[mid+1] > key))
return mid;
else
low = mid + 1;
}
}
return -1;
}