二分查找
- 1 二分查找的框架
- 2 尋找一個數(基本的二分搜索)
- 3 尋找左側邊界的二分搜索
- 4 尋找右側邊界的二分查找
- 5 合并
二分查找場景:有序數組尋找一個數、尋找左側邊界(有序數組第一個等目標數的下標)、尋找右側邊界(有序數組最后一個等于目標數的下標)
1 二分查找的框架
int binarySearch(int *nums,int numsSize,int target)
{int left=0,rigth = ...;while(...){int mid = left +(rigth-left)/2;if(nums[mid] == target){...}else if (nums[mid]<target){left = ...;}else if (nums[mid]>target){rigth = ...;}}return ...
}
使用else if是為了把所有情況寫清楚,這樣可以清楚的展現所有細節。本文都使用else if,旨在說清楚,可自行簡化。
其中…標記的部分,就是可能出現細節問題的地方,當你見到一個二分查找的代碼時,首先要注意這幾個地方。
2 尋找一個數(基本的二分搜索)
這個場景是最簡單的,即搜索一個數,如果存在,返回其索引,否則返回-1。
int binarySearch(int *nums,int numsSize,int target)
{int left=0;int right = numsSize-1; //注意while(left<=rigth) //注意{int mid = left +(right-left)/2;if(nums[mid] == target)return mid;else if(nums[mid]<target){left = mid+1; //注意}else if(nums[mid]>target){right = mid+1; //注意}}return -1;
}
- 為什么while的循環條件中是<=,而不是<?
答:因為初始化right的賦值是numsSize-1,即最后一個元素的索引,而不是numsSize,這二者可能出現在不同功能的二分查找中,區別是:前者相當于兩端都閉區間[left,right],后者相當于左閉右開區間[left,right),因為索引大小為numsSize是越界的。
我們這個算法中使用的是[left,right]兩端都閉的區間,這個區間就是每次進行搜索的區間,那while循環什么時候應該終止?搜索區間為空的時候應該終止,意味著你沒的找了。
while(left<=right)終止條件是left==right+1,寫成區間的形式是[ right+1,right],這個時候搜索區間為空,直接返回-1.
while(left<right)的終止條件是left==right,寫成區間的形式是[right,right],者時候區間非空,還有一個數nums[right],如果此時while循環停止了,就漏掉一個數,如果這個時候返回-1就可能出現錯誤。
- 為什么left=mid+1,right=mid-1?
本算法的搜索區間兩端都是閉的,即[left,right]。那么當我們發現索引mid不是要找的target時,如果確定下一步的搜索區間呢?
當然是去搜索[left,mid+1]或者[mid+1,right],因為mid已經搜索過了,應該從搜索區間去除。 - 此算法有什么缺陷?
比如說你有有序數組nums=[1,2,2,2,3],此算法返回的索引是2,但是如果我想得到target的左側邊界,即索引1,或者想得到target的右側邊界,即索引3,這樣的話,此算法是無法處理的。
3 尋找左側邊界的二分搜索
查找左側邊界的數,即在一個有序數組中,找到第一個等于target的下標。比如數組int nums[]={5,7,7,8,8,8,10},target=8,第一個等于8的下標是3,第一個大于等于8的數組下標也是3。即找到第一個等于target的數等價于 找第一個大于等于targte的數的下標,然后我們判斷該下標所對應的數是否是target。
直接看代碼:
/* 二分查找左側邊界 */
int lower_bound(int *nums,int numsSize,int target)
{int left=0,right=numsSize-1,ans=numsSize;while(left<=right){int mid = left+(right-left)/2;if(nums[mid]>=target){right = mid-1;ans = mid;}else {left = mid+1;}}return ans;
}
int main(void)
{int nums[]={5,7,7,8,8,8,10};int ret;//int p = high_bound(nums,7,8);//printf("%d \n",p);ret = lower_bound(nums,7,8);printf("%d \n",ret);return 0;
}
輸出是3。
當nums[mid]>=target
時,說明有大于等于target的數了,我們需要更新ans來記錄大于等于targte的數,right需要更新,然后繼續往在[left,mid-1]區間找大于等于target的數,如果nums[mid]<target
,則需要在[mid+1,right]區間找大于等于target的數,left下標所指向的值始終是小于等于target(如果全部數據全部大于target,left將不會變化),所以,當結束時,ans所指向的值是[left,right]區間最后一個大于等于target的值,left下標所指向的值又始終是小于等于target,所以ans所指向的內容是第一個大于等于target的值。
4 尋找右側邊界的二分查找
尋找右側邊界的數,就是找第一個大于target的數,返回其下標減1,int nums[]={5,7,7,8,8,8,10},最后一個等于8的下標是5,第一個大于8的數是10,其下標是6,減去1是5,找target最后位置等價于找第一個大于target的下標減1,然后判斷該位置上的數是否等于target。
具體代碼為:
/* 二分查找右側邊界 */
int high_bound(int *nums,int numsSize,int target)
{int left=0,right=numsSize-1,ans=numsSize;while(left<=right){int mid = left+(right-left)/2;if(nums[mid]>target){right = mid-1;ans = mid;}else {left = mid+1;}}return ans;
}
int main(void)
{int nums[]={5,7,7,8,8,8,10};int ret;//int p = high_bound(nums,7,8);//printf("%d \n",p);ret = high_bound(nums,7,8)-1;printf("%d \n",ret);return 0;
}
運行結果是5。
如果nums[mid]>target,有大于target的數了,ans就要去記錄,right更新,right=mid-1 ,如果nums[mid]<=target,則left需要更新,left=mid-1,left指示的內容始終是小于等于target的(如果數據全部大于target,left不會變)。只要left<=right,就會一直縮小區間,當運行結束后,ans所指示的內容是最后一個大于target的數。
5 合并
我們添加一個參數,表示找第一個等于target的數,還是找最后一個等于target的數。
int binarySearch(int* nums, int numsSize, int target, bool lower) {int left = 0, right = numsSize - 1, ans = numsSize;while (left <= right) {int mid = (left + right) / 2;if (nums[mid] > target || (lower && nums[mid] >= target)) {right = mid - 1;ans = mid;} else {left = mid + 1;}}return ans;
}
當lower為真時,表示找第一個大于等于target的數,當lowe為假時,表示找第一個大于target的數,返回之后,檢查和上面代碼一樣。
leedcode的第34題:在排序數組中查找元素的第一個和最后一個位置
示例代碼:
/*
在一個升序數組中,找第一個等于target的數組,即找第一個大于等于target的數,返回其下標,最后判斷
是否等于targettarget。
找最后一個等于target的數組,即找第一個大于target的數,返回其下標減1,最后判斷該下標對應的數是否等于target。
*/
int binarySearch(int *nums,int numsSize,int target,bool lower)
{int left=0,right=numsSize-1,ans=numsSize;while(left<=right){int mid=left+(right-left)/2;if(nums[mid]>target || (lower && nums[mid]>=target)){right=mid-1;ans = mid;}else{left=mid+1;}}return ans;
}
/*** Note: The returned array must be malloced, assume caller calls free().*/
int* searchRange(int* nums, int numsSize, int target, int* returnSize){int leftIdx = binarySearch(nums,numsSize,target,true);int rightIdx = binarySearch(nums,numsSize,target,false)-1;int *ret = (int *)malloc(sizeof(int)*2);*returnSize=2;if(leftIdx<=rightIdx && nums[leftIdx]==target && nums[rightIdx]==target){ret[0]=leftIdx;ret[1]=rightIdx;return ret;}ret[0]=-1;ret[1]=-1;return ret;
}