之前所说的lower_bound和upper_bound其实就是二分查找的一种扩展形式,在SGI STL里面,二分查找算法实际调用的是lower_bound。所以lower_bound和upper_bound还是非常重要的。主要用到的是二分查找的思想。
一、二分查找:
输入:有序的一个数组,一个待查找的元素。
输出:待查找元素是否在这个数组里面。
每次都会利用到数组的有序性,丢弃数组中一半的数据。这点非常重要,在他 的扩展形式中,每次也是对于确定不需要查找的一半元素进行丢弃。下面详细说明。
首先给出二分查找的算法:
int binary_search_my(const int A[],int length,const int v){
int left = 0,right = length - 1;
while(left <= right){//= should be reached because length=1
// int mid = (left + right)/2;
int mid = left + (right - left)/2;//avoid over flow of+
if(nums[mid] == v)
return 1;
else if(nums[mid] > v)//v cann't locate [mid,right]
right = mid -1;
else
left = mid +1;//v cann't locate [left,mid]
}
return 0;
}
输入的时候:
A[0,length−1],A[0]≤A[1]≤⋯≤A[length−1]
循环:
A[first]≤A[mid]≤A[right],first≤right
当只有一个元素的时候查找也需要成立,所以
=
是能够取到的。因为
- 当
v==A[mid] 的时候,说明找到了这个元素。
- 当 v<A[mid]≤A[mid+1]⋯≤A[right] 的时候,说明这个元素不可能在 A[mid,right] 之间,所以 v 可能存在的区间为
[first,mid−1] 。- 当 v>A[mid]≥A[mid−1]⋯≥A[first] 的时候,说明这个元素不可能在 A[first,mid] 之间,所以 v 可能存在的区间为
[mid+1,right] 。
结束:如果找到待查找元素,那么在循环 first≤right 的时候就停止了,否则 first>right 时候结束。
此外,给出 STL形式的 [first,last) 写法
int binary_search_my(const int A[],int length,int v){ int first = 0,last = length; while(first != last){ int mid = (first + last)/2; if(A[mid] == v) return 1; else if(A[mid] > v) last = mid ; else first = mid +1; } return 0; }
二、旋转数组的最小值(无重复)
题目来源
Suppose a sorted array is rotated at some pivot unknown to you beforehand.
(i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).
Find the minimum element.
You may assume no duplicate exists in the array.
对于一个排序好的数组,在某个位置进行了旋转,所以最小元素不一定在数组的开始了,而可能在数组的中间。
- 输入: A[left⋯right],
- A[left]<A[left+1]⋯A[mid−1]>A[min]|<A[min+1]⋯<A[right]
- min 表示最小元素所在的位置。
输出:最小元素
可以通过min这个位置把整个数组分为两个部分 A[first,min−1],A[min,right] 可以看出来,这两个部分都是有序。 first 始终指向第一个数组的开始, last 始终指向第二个数组的结束。如果 first==right 的时候,可以认为结束查找,因为这个数肯定就是最小值。数组部分有序,可以考虑使用二分查找。
- 所以对于中间元素 mid=(left+right)/2,left≤mid<right
因为 mid≠right ,所以不存在 A[mid]==A[right] ,但是可能存在 A[first]==A[mid]
所以我们可以根据信息来丢弃一半最小元素肯定不在里面的那部分。
如果 A[mid]>A[right] 那么说明最小元素不可能在 [first,mid] 当中,包括 A[mid] 不可能是最小元素,因为最小元素肯定是要小于 A[right] 的。所以这个时候查找的区间为 [mid+1,right]
否则: A[mid]<A[right] 为什么是小于而不是小于等于呢?因为 mid≠right 所以等号是取不到的。这个时候最小值,可能就是 A[mid] 所以下一步的查找区间变为 [first,mid] 这个时候需要包含 mid 因为, A[mid] 可能就是最小值。所以根据上面的分析写代码如下:
int rotatemin(const int A[],int length){ int left = 0,right = length-1; while(left < right){//when left == right,right is mininum int mid = (left + right )/2;//may overflow of int if(A[mid] > A[right])//[mid+1,right] left = ++mid; else right = mid;//[left,mid],mid may be mininum } return A[left]; }
我们来分析一下,如果是和前一部分的区间进行比较,该如何进行丢弃一半的区间
- mid=(left+right)/2
如果 A[mid]>A[first] 说明 A[mid] 在前部分的有序区间,最小元素在 [mid+1,right] ,但是如果 [first,right] 在运行过程当中, first 越过在前面的有序数组的最后位置,那么这个时候 [first,last] 就是有序的数组了,那么下面的分析,就没有意义了,为了防止这种情况的发生,只要 first 越过前面有序数组的位置,那么我们就可以判断这个时候 first 指向了后半部分有序数组的第一个位置,也就是最小值的位置,这个时候终止。
如果 A[mid]==A[first] 由于数组当中没有重复元素,所以这个时候说明 mid==first 说明 first+1==right ,最小元素是 A[right] 前提是, first 指向前半部分的有序数组。
如果 A[mid]<A[first] 最小元素不可能在 [mid+1,right] 所以最小元素在 [first,mid] 注意这个时候如果 A[mid] 刚好是最小元素时,所以这个位置还是不能被丢弃。- 从上面的分析可以看出来,如果是和 first 进行比较还是情况比较多的,也比较麻烦,所以选择和 right 进行比较还是简单的,可以十分确定丢弃最小值不可能存在的区间的一半元素,正好符合二分查找的思想。下面是上面分析的代码。
int rotatemin_first(const int A[],const int length){ int left =0,right = length-1; while(left != right){ if(A[left] < A[right])//[first,right] is sorted.. return A[left]; int mid = (left+right)/2; if(A[mid] > A[left]) left = mid +1; else if(A[mid] < A[left]) right = mid; else{ return A[right];//mid==first so first +1 ==right } } return A[left]; }