二分查找,如何用最勝內存的方式實現快速查找
針對有序數據集合的查找算法,二分查找。
問題:假設我們有1000萬個整數數據,每個數據佔8個字節,如何設計數據結構和算法快速判斷某個整數是否出現在這1000萬數據中,
最簡單的辦法就是將數據存儲在數組中,內存佔用差不多80M,符合內存限制,所以,先對數據從小到大排序,然後利用二分查找算法,就可以快速查找想要的數據。
二分查找針對的是一個有序的數據集合,查找思想類似分治思想,每次都通過跟區間的中間元素對比,將待查找的區間縮小爲之前的一半,直到找到要查找的元素,或者區間縮小爲0
二分查找時間複雜度低,O(logn),
二分查找最簡單的就是有序數組中不存在重複元素。
二分查找依賴的是順序表結構,就是數組。還針對的是有序數據。
二分查找只能再插入,刪除不頻繁,一次排序多次查找的場景中,針對動態變化的數據集合,二分查找將不再適用。
數據量太小不適合二分查找。
數據量太大也不適合二分查找,因爲底層依賴數組這種數據結構,而數組爲了支持隨機訪問,要求內存空間連續,對內存的要求苛刻。
使用遞歸實現二分查找
public static int recursionBinarySearch(int[] arr,int key,int low,int high){
if(key<arr[low] || key> arr[high] || low > high){
return -1;
}
int middle = low + ((high - low)>>1);
//middle = (low + high) / 2; 這種寫法有可能會導致溢出,比如low很大,high也很大,之和就溢出了。
if(arr[middle] > key){
return recursionBinarySearch(arr,key,low,middle);
}
else if(arr[middle] < key){
return recursionBinarySearch(arr,key,middle + 1,high);
}else {
return middle;
}
}
普通的二分查找。
public static int commonBinarySearch(int[] arr,int key){
int low = 0;
int high = arr.length -1;
int middle = 0;
if(key < arr[low] || key> arr[high] || low > high){
return -1;
}
while(low <=high){
middle = low + ((high - low)>>1);
//middle = (low + high) / 2; 這種寫法有可能會導致溢出,比如low很大,high也很大,之和就溢出了。
if(arr[middle] > key){
high = middle -1;
}else if(arr[middle] < key){
low = middle + 1;
}else {
return middle;
}
}
return -1;
}
變體一,查找第一個值等於給定值的元素
public static int bsearchFirst(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 {
low = mid + 1;
}
}
if (low < n && a[low]==value) return low;
else return -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;
}
變體二,查找第一個值等於給定值的元素
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;
}
變體三,查找第一個大於等於給定值的元素
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;
}
變體四,查找最後一個小於等於給定值的元素
public int bsearchFour(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;
}
如果有序數組是一個循環有序數組,比如4.5.6,1,2,3這種情況。
//無重複元素
public int search(int[] nums, int target)
{
int start = 0,end = nums.length-1;
while(start<=end)
{
int mid = (start+end)/2;
if(target == nums[mid]){return mid;}
if(nums[start]<=nums[mid])//說明start-mid之間是有序的
{
if(nums[start]<=target && target<nums[mid])
{
end = mid-1;
}else
start = mid+1;
}else
{
if(target>nums[mid] && target<=nums[end])
{
start = mid+1;
}else
end = mid-1;
}
}
return -1;
}
//有重複數字
public boolean searchCycle(int[] nums, int target) {
int start = 0,end = nums.length-1;
while(start<=end)
{
int mid = (start+end)/2;
if(target == nums[mid]){return true;}
if(nums[mid]>nums[start])
{
if(target>=nums[start] && target<nums[mid])
{
end = mid-1;
}else
{
start = mid+1;
}
}else
if(nums[mid] == nums[start])
{
while(start<=mid)
{
if(nums[start] == target){return true;}
if(nums[start] != nums[mid])
{
break;
}
start++;
}
}else
{
if(target>nums[mid] && target<=nums[end])
{
start = mid+1;
}else
end = mid-1;
}
}
return false;
}