二分查找法及其扩展运用
二分查找可以解决排序数组的查找问题:只要数组中包含T(即要查找的值),那么通过不断缩小包含T的范围,最终就可以找到它。一开始,范围覆盖整个数组。将数组的中间项与T进行比较,可以排除一半元素,范围缩小一半。就这样反复比较,反复缩小范围,最终就会在数组中找到T,或者确定原以为T所在的范围实际为空。对于包含N个元素的表,整个查找过程大约要经过logN次比较。
二分搜索需要注意开闭区间的问题,限制条件和边界要保持配对:low <= high , low = mid +1 ,high = mid-1。二分搜索的模板如下:
//二分查找法template <class T> int binary_search(T array[], T value, int size){ int high = size-1, low = 0, mid=0; while (low <= high) { mid = mid + (high - low) >> 1; if (value < array[mid]) high = mid - 1; else if(value > array[mid]) low = mid + 1; else return mid; } return -1;}
题目:有一特殊数组,它是循环递增的,并且是严格递增的,如A[]={ 17 19 20 25 1 4 7 9},试在这样的数组中找一元素x,查找是否存在。
对于循环有序的数组,它是循环递增的,也可以使用二分搜索:每次将数组分为2部分,一个是单调递增的,一个是循环递增的。如果是单调递增的话直接使用朴素的二分搜索,如果是循环递增的话继续使用特殊二分搜索下去。
思路分析:
在循环递增数组中,我们不能简单地通过与数组中间元素的大小关系来确定要检索的元素所落在的区间范围。要确定范围,我们可以再加上要检索的元素与数组两端的元素的大小关系。
循环递增数组有这么一个性质:以数组中间元素将循环递增数组划分为两部分,则一部分为一个严格递增数组,而另一部分为一个更小的循环递增数组。当中间元素大于首元素时,前半部分为严格递增数组,后半部分为循环递增数组;当中间元素小于首元素时,前半部分为循环递增数组;后半部分为严格递增数组。
实现:
记要检索的元素为key,数组的首元素为a[low],中间元素为a[mid],末尾元素为a[high]。则当key不等于a[mid]时:
1、a[mid] > a[low],即数组前半部分为严格递增数组,后半部分为循环递增数组时,若key小于a[mid]并且不小于a[low]时,则key落在数组前半部分;否则,key落在数组后半部分。
2、a[mid] < a[high],即数组前半部分为循环递增数组,后半部分为严格递增数组时,若key大于a[mid]并且不大于a[high]时,则key落在数组后半部分;否则,key落在数组前半部分。
实现代码如下:
//循环严格递增数组的二分搜索 int Search(int A[],int n, int num) { int left = 0 ; int right = n - 1 ; int mid = 0 ; int pos = -1 ; //返回-1,表示查找失败 while(left <= right) { mid = (left + right)/2 ; if (A[mid] == num) { pos = mid ; break; } if (A[left] <= A[mid]) //前半部分是严格递增的,后半部分是一个更小的循环递增数组 { if(A[left] <= num && num < A[mid] ) { right = mid - 1 ; } else { left = mid + 1 ; } } else //后半部分是严格递增的,前半部分是一个更小的循环递增数组 { if ( A[mid] < num && num <= A[right]) { left = mid + 1 ; } else { right = mid - 1 ; } } } return pos; }
题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个排好序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1。
分析:这道题最直观的解法并不难。从头到尾遍历数组一次,就能找出最小的元素,时间复杂度显然是O(N)。但这个思路没有利用输入数组的特性,我们应该能找到更好的解法。
我们注意到旋转之后的数组实际上可以划分为两个排序的子数组,而且前面的子数组的元素都大于或者等于后面子数组的元素。我们还可以注意到最小的元素刚好是这两个子数组的分界线。我们试着用二元查找法的思路在寻找这个最小的元素。
我们得到处在数组中间的元素。如果该中间元素位于前面的递增子数组,那么它应该大于或者等于第一个指针指向的元素。此时数组中最小的元素应该位于该中间元素的后面。我们可以把第一指针指向该中间元素,这样可以缩小寻找的范围。同样,如果中间元素位于后面的递增子数组,那么它应该小于或者等于第二个指针指向的元素。此时该数组中最小的元素应该位于该中间元素的前面。我们可以把第二个指针指向该中间元素,这样同样可以缩小寻找的范围。我们接着再用更新之后的两个指针,去得到和比较新的中间元素,循环下去。
// Get the minimum number of a roatation of a sorted arrayint Min(int *numbers, int length){ int leftIndex = 0; int rightIndex = length-1; if(length == 1) return numbers[0]; while(leftIndex < rightIndex) { if(rightIndex - leftIndex == 1) return numbers[rightIndex]>numbers[leftIndex] ? numbers[leftIndex] : numbers[rightIndex]; int middle = (leftIndex+rightIndex)/2; if(numbers[middle] > numbers[leftIndex]) leftIndex = middle; else if(numbers[middle] < numbers[leftIndex]) rightIndex = middle; }}
题目:有两个已排好序的数组A和B,长度均为n,找出这两个数组合并后的中间元素,要求时间代价为O(logn)。
解析:这个题目看起来非常简单。第一题的话: 假设数组长度为n, 那么我就把数组1和数组2直接合并,然后再直接找到中间元素。对于这样的方案,时间复杂度就是O(n)。通常在这样的情况下,那些要求比较高的面试官就会循循善诱道:“你还有更好的办法吗?” 如果比线性更高效,直接能想到的就是对数了O(logn),这个时间复杂度在这里可能吗? 当然还是可能的。
算法导论上面的分析是这样的:
Say the two arrays are sorted and increasing, namely A and B.It is easy to find the median of each array in O(1) time.Assume the median of array A is m and the median of array B is n. Then,
1. If m==n,then clearly the median after merging is also m,the algorithm holds.
2. If m<=n,then reserve the half of sequence A in which all numbers are greater than m,also reserve the half of sequence B in which all numbers are smaller than n.
Run the algorithm on the two new arrays。
3. If m>n,then reserve the half of sequence A in which all numbers are smaller than m,also reserve the half of sequence B in which all numbers are larger than n.
Run the algorithm on the two new arrays。
Time complexity: O(logn)
/ 两个长度相等的有序数组寻找中位数 int Find_Media_Equal_Length(int a[] , int b[] , int length) { if(length == 1) { return a[0] > b[0] ? b[0] : a[0]; } int mid = (length-1)/2; //奇数就取中间的,偶数则取坐标小的 if(a[mid] == b[mid]) return a[mid]; else if(a[mid] < b[mid]) { //偶数则取剩下的length/2,奇数则取剩下的length/2+1 return Find_Media_Equal_Length(&a[length-mid-1] , &b[0] , mid+1); //return Find_Media_Equal_Length(a+length-mid-1 , b , mid+1); } else { return Find_Media_Equal_Length(&a[0] , &b[length-mid-1] , mid+1); //return Find_Media_Equal_Length(a , b+length-mid-1 , mid+1); } } 非递归代码如下:// 非递归代码 int Find_Media_Equal_Length(int a[] , int b[] , int length) { int mid; while(1) { if(length == 1) { return a[0] > b[0] ? b[0] : a[0]; } mid = (length-1)/2; if(a[mid] == b[mid]) return a[mid]; else if(a[mid] < b[mid]) a = a + length - mid - 1; // a数组的后半部分 else b = b + length - mid - 1; // b数组的后半部分 length = mid + 1; }}
参考:http://blog.csdn.net/hackbuteer1/article/details/7584838