【转】二分
原创:http://blog.chinaunix.net/uid-1844931-id-3337784.html
前几天在论坛上看到有统计说有80%的程序员不能够写对简单的二分法。二分法不是很简单的吗? 这难道不是耸人听闻?
其实,二分法真的不那么简单,尤其是二分法的各个变种。 最最简单的二分法,就是从一个排好序的数组之查找一个key值。 如下面的程序:
1 int search(int *arr, int n, int key){ 2 int left=0, right=n-1; 3 //首先要把握下面几个要点: 4 //right=n-1 => while(left <= right) => right=mid-1; 5 //right=n => while(left < right) => right=mid; 6 while(left<=right){ 7 int mid=left+((right-left)<<1);//防止溢出,移位也更高效。 8 if(arr[mid]>key) right=mid-1; 9 else if (arr[mid]<key) left=mid+1; 10 else return mid; 11 //数组中不相等的情况更多 12 //如果每次循环都判断一下是否相等,将耗费时间 13 } 14 return -1; 15 }
这个程序,相信只要是一个合格的程序员应该都会写。 稍微注意一点, 每次移动left和right指针的时候,需要在mid的基础上+1或者-1, 防止出现死循环, 程序也就能够正确的运行。
但如果条件稍微变化一下, 你还会写吗?如,数组之中的数据可能可以重复,要求返回匹配的数据的最小(或最大)的下标;更近一步, 需要找出数组中第一个大于key的元素(也就是最小的大于key的元素的)下标,等等。 这些,虽然只有一点点的变化,实现的时候确实要更加的细心。 下面列出了这些二分检索变种的实现。
1. 找出第一个与key相等的元素
1 int searchFirstEqual(int *arr, int n, int key){ 2 int left=0,right=n-1; 3 while(left<=right) { 4 int mid=(left+right)/2; 5 if(arr[mid]>=key) right=mid-1; 6 else if(arr[mid]<key) left=mid+1; 7 } 8 if(left<n&&arr[left]==key) return left; 9 return -1; 10 }
2. 找出最后一个与key相等的元素
1 int searchLastEqual(int *arr, int n, int key){ 2 int left=0,right=n-1; 3 while(left<=right) { 4 int mid=(left+right)/2; 5 if(arr[mid]>key) right=mid-1; 6 else if(arr[mid]<=key) left=mid+1; 7 } 8 if(right>=0&&arr[right]==key) return right; 9 return -1; 10 }
3. 查找第一个等于或者大于Key的元素
1 int searchFirstEqualOrLarger(int *arr,int n,int key){ 2 int left=0, right=n-1; 3 while(left<=right) { 4 int mid=(left+right)/2; 5 if(arr[mid]>=key) right=mid-1; 6 else if (arr[mid]<key) left=mid+1; 7 } 8 return left; 9 }
4. 查找第一个大于key的元素
1 int searchFirstLarger(int *arr, int n, int key){ 2 int left=0, right=n-1; 3 while(left<=right) { 4 int mid=(left+right)/2; 5 if(arr[mid]>key) right=mid-1; 6 else if (arr[mid]<=key) left=mid+1; 7 } 8 return left; 9 }
5. 查找最后一个等于或者小于key的元素
1 int searchLastEqualOrSmaller(int *arr,int n,int key){ 2 int left=0,right=n-1; 3 while(left<=right) { 4 int m=(left+right)/2; 5 if(arr[m]>key) right=m-1; 6 else if (arr[m]<=key) left=m+1; 7 } 8 return right; 9 }
6. 查找最后一个小于key的元素
1 int searchLastSmaller(int *arr,int n,int key){ 2 int left=0, right=n-1; 3 while(left<=right) { 4 int mid=(left+right)/2; 5 if(arr[mid]>=key) right=mid-1; 6 else if(arr[mid]<key) left=mid+1; 7 } 8 return right; 9 }
下面是一个测试的例子:
1 int main(){ 2 int arr[17] = {1, 3 2, 2, 5, 5, 5, 4 5, 5, 5, 5, 5, 5 5, 5, 6, 6, 7}; 6 printf("First Equal : %2d \n", searchFirstEqual(arr, 16, 5)); 7 printf("Last Equal : %2d \n", searchLastEqual(arr, 16, 5)); 8 printf("First Equal or Larger : %2d \n", searchFirstEqualOrLarger(arr, 16, 5)); 9 printf("First Larger : %2d \n", searchFirstLarger(arr, 16, 5)); 10 printf("Last Equal or Smaller : %2d \n", searchLastEqualOrSmaller(arr, 16, 5)); 11 printf("Last Smaller : %2d \n", searchLastSmaller(arr, 16, 5)); 12 return 0; 13 }
最后输出结果是:
First Equal : 3
Last Equal : 12
First Equal or Larger : 3
First Larger : 13
Last Equal or Smaller : 12
Last Smaller : 2
很多的时候,应用二分检索的地方都不是直接的查找和key相等的元素,而是使用上面提到的二分检索的各个变种,熟练掌握了这些变种,当你再次使用二分检索的检索的时候就会感觉的更加的得心应手了。
搜狗还有一道面试题目:
有一个数组,该数组的特点是:前一部分递增有序,后一部分递减有序,求数组的峰值位置。(严格先增后减)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 //数组是先增后减数组。 3 int findMax(int *a ,int n){ 4 int low = 0,high = n-1,mid; 5 while(low <= high){ 6 mid = low + ((high-low)>>1); 7 if((mid + 1)<=high && (mid-1)>=low){ 8 if(a[mid] >= a[mid-1] && a[mid] >= a[mid + 1]){ 9 return mid; 10 }else if(a[mid] >= a[mid-1] && a[mid] <=a[mid + 1]){ 11 low = mid + 1; 12 }else{ 13 high = mid - 1; 14 } 15 }else if((mid+1) <= high){ 16 if(a[mid] <= a[mid+1]){ 17 return mid+1; 18 }else{ 19 return mid; 20 } 21 }else{ 22 if(a[mid] <= a[mid-1]){ 23 return mid-1; 24 }else{ 25 return mid; 26 } 27 } 28 29 } 30 } 31 32 int main(){ 33 int a[] = {1,2,7,6,5,4,3,1}; 34 printf("%d \n",findMax(a,8)); 35 }