二分查找小结

  在弄dp时感觉一道题需非要弄清二分查找不可。以前学二分一直就很迷惑,网上资料也各种各样。的确二分是个很容易写错的算法,今天只好不算太耐心的再看一遍二分。总感觉时间不够用。。

  二分查找有许多细节,这次先抓主要矛盾。关于什么(left+rigth)/2溢出的问题啊先不考虑了。对我来说二分迷惑的地方还是在1.while(left?right) ?处到底是<还是<= 2.判断后mid到底是加一还是减一还是不变? 3.返回left还是right?

  这次大概明白了一些,因为二分查找是看区间开闭的,对于左闭右开[l,r)一般是left<right,对于左闭又闭[l,r]一般是left<=right.这里区间开闭是针对数组下标而言,比如a={1,2,3,4,5},数组下标在[0,4](a[0]到a[4]),[0,4]就是左闭又闭区间,而[0,5)就是左闭右开区间。看似其实查询的都是同一个数组,前一种方式初始时left=0,right=4,后一种方式初始时left=0,right=5。这小小的差别就会造成很大的隐患bug。

经典的查询key是否在数组中,是返回下标否则返回-1:

 1 int BinarySearch(int array[],int n,int key)
 2 {
 3     int left=0,right=n-1;
 4     while(left<=right)
 5     {
 6         int mid=(left+right)>>1;
 7         if(array[mid]>key) 
 8             right=mid-1;
 9         else if(array[mid]<key) 
10             left=mid+1;
11         else return mid;
12     }
13     return -1;
14 }

这里是左闭又闭区间查找,此时:

  1.一定要是 while(left<=right)。因为若是 while(left<right)可能找不到key。例如array={1,2,3,4,5},key=5当left==right时才找到key。

  2.一定要是 left=mid+1; 否则可能死循环。如上面例子,当left指向4时,right指向5,两个指针相邻mid永远等于left,发生死循环。产生死循环的根本原因在于left,因为left可能永远等于mid,而right不会因为等于mid死循环。所以这里我觉得right也一定要减一。其实这个代码看上去是很好理解的,就是大于双闭闭区间查找,大于key就在[left,mid-1]中找,小于key就在[mid+1,right]中找。

当改成左闭右开区间时,需要修改:

  1.循环的条件是while(left<right)

  2.循环内当array[mid]>key 时,right=mid

至于这些细节,以后有时间(不知道会不有>_<..)再细抠。

下面记录一下经典变形(采用左闭右闭写法):

1.找出第一个与key相等的元素:

 1 int searchFirstEqual(int *arr, int n, int key)
 2 {
 3     int left = 0, right = n - 1;
 4     while (left <= right) {
 5         int mid = (left + right) >> 1;
 6         if (arr[mid] >= key) right = mid - 1;
 7         else if (arr[mid] < key) left = mid + 1;
 8     }
 9     if (left < n&&arr[left] == key) return left;
10     //arr[right]<key<=arr[left]
11     //right是最后一个小于key的
12     //left是第一个大于等于key的
13     return -1;
14 }

2.找出最后一个与key相等的元素

 1 int searchLastEqual(int *arr, int n, int key)
 2 {
 3     int left = 0, right = n - 1;
 4     while (left <= right) {
 5         int mid = (left + right) >> 1;
 6         if (arr[mid] > key) right = mid - 1;
 7         else if (arr[mid] <= key) left = mid + 1;
 8     }
 9     //arr[right]<=key<arr[left]
10     //right是最后一个小于等于可以的
11     //left是第一个大于key的
12     if (right >= 0 && arr[right] == key) return right;
13     return -1;
14 }

3.查找第一个等于或大于key的元素

例如 arr={1,2,2,2,4,8,10},查找2,返回第一个2的下标1;查找3,返回4的下标4,查找4,返回4的下标4.

解释:条件为arr[mid]>=key,意思是key小于等于中间值,则左半区查找。如在arr中查找2.第一步,left=0,right=6,则mid=3,arr[mid]>=key,往左半部分{1,2,2}中继续查找。终止前一步为:left=right,则mid=left,若arr[mid]>=key,则right会变,而left指向当前元素,即满足要求的元素;若arr[mid]<key,则left会变,而left指向mid的下一个元素。

 1 int searchFirstEqualOrLarger(int *arr, int n, int key)
 2 {
 3     int left = 0, right = n - 1;
 4     while (left <= right) {
 5         int mid = (left + right) >> 1;
 6         if (arr[mid] >= key) right = mid - 1;
 7         else if (arr[mid] < key) left = mid + 1;
 8     }
 9     return left;
10 }

4.查找第一个大于key的元素

例如:int[] a = {1,2,2,2,4,8,10},查找2,返回4的下标4;查找3,返回4的下标4;查找4,返回8的下标5。与上面的代码仅一个等于符号不同。

 1 int searchFirstLarger(int *arr, int n, int key)
 2 {
 3     int left = 0, right = n - 1;
 4     while (left <= right) {
 5         int mid = (left + right) >> 1;
 6         if (arr[mid] > key) right = mid - 1;
 7         else if (arr[mid] <= key) left = mid + 1;
 8     }
 9     return left;
10 }

5.查找最后一个等于或者小于key的元素

 1 int searchLastEqualOrSmaller(int *arr, int n, int key)
 2 {
 3     int left = 0, right = n - 1;
 4     while (left <= right) {
 5         int mid = (left + right) >> 1;
 6         if (arr[mid] > key) right = mid - 1;
 7         else if (arr[mid] <= key) left = mid + 1;
 8     }
 9     return right;
10 }

6.查找最后一个小于key的元素

 1 int searchLastSmaller(int *arr, int n, int key)
 2 {
 3     int left = 0, right = n - 1;
 4     while (left <= right) {
 5         int mid = (left + right) >> 1;
 6         if (arr[mid] >= key) right = mid - 1;
 7         else if (arr[mid] < key) left = mid + 1;
 8     }
 9     return right;
10 }

完整测试程序:

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<algorithm>
  5 using namespace std;
  6 const int MAXN = 107;
  7 int dp[MAXN][MAXN];
  8 char s[MAXN];
  9 
 10 
 11 
 12 int search(int *arr, int n, int key)
 13 {
 14     int left = 0, right = n - 1;
 15     while (left <= right) {
 16         int mid = (left + right) >> 1;
 17         if (arr[mid] == key) return mid;
 18         else if (arr[mid] > key) right = mid - 1;
 19         else left = mid + 1;
 20     }
 21     return -1;
 22 }
 23 
 24 //1.找出第一个与key相等的元素
 25 int searchFirstEqual(int *arr, int n, int key)
 26 {
 27     int left = 0, right = n - 1;
 28     while (left <= right) {
 29         int mid = (left + right) >> 1;
 30         if (arr[mid] >= key) right = mid - 1;
 31         else if (arr[mid] < key) left = mid + 1;
 32     }
 33     if (left < n&&arr[left] == key) return left;
 34     //arr[right]<key<=arr[left]
 35     //right是最后一个小于key的
 36     //left是第一个大于等于key的
 37     return -1;
 38 }
 39 
 40 
 41 //2.找出最后一个与key相等的元素
 42 int searchLastEqual(int *arr, int n, int key)
 43 {
 44     int left = 0, right = n - 1;
 45     while (left <= right) {
 46         int mid = (left + right) >> 1;
 47         if (arr[mid] > key) right = mid - 1;
 48         else if (arr[mid] <= key) left = mid + 1;
 49     }
 50     //arr[right]<=key<arr[left]
 51     //right是最后一个小于等于可以的
 52     //left是第一个大于key的
 53     if (right >= 0 && arr[right] == key) return right;
 54     return -1;
 55 }
 56 
 57 //3.查找第一个等于或大于key的元素
 58 int searchFirstEqualOrLarger(int *arr, int n, int key)
 59 {
 60     int left = 0, right = n - 1;
 61     while (left <= right) {
 62         int mid = (left + right) >> 1;
 63         if (arr[mid] >= key) right = mid - 1;
 64         else if (arr[mid] < key) left = mid + 1;
 65     }
 66     return left;
 67 }
 68 
 69 //4.查找第一个大于key的元素
 70 int searchFirstLarger(int *arr, int n, int key)
 71 {
 72     int left = 0, right = n - 1;
 73     while (left <= right) {
 74         int mid = (left + right) >> 1;
 75         if (arr[mid] > key) right = mid - 1;
 76         else if (arr[mid] <= key) left = mid + 1;
 77     }
 78     return left;
 79 }
 80 
 81 //5.查找最后一个等于或者小于key的元素
 82 int searchLastEqualOrSmaller(int *arr, int n, int key)
 83 {
 84     int left = 0, right = n - 1;
 85     while (left <= right) {
 86         int mid = (left + right) >> 1;
 87         if (arr[mid] > key) right = mid - 1;
 88         else if (arr[mid] <= key) left = mid + 1;
 89     }
 90     return right;
 91 }
 92 
 93 //6.查找最后一个小于key的元素
 94 int searchLastSmaller(int *arr, int n, int key)
 95 {
 96     int left = 0, right = n - 1;
 97     while (left <= right) {
 98         int mid = (left + right) >> 1;
 99         if (arr[mid] >= key) right = mid - 1;
100         else if (arr[mid] < key) left = mid + 1;
101     }
102     return right;
103 }
104 
105 
106 
107 int main()
108 {
109     int arr[17] = { 1,2,2,5,5,5,5,5,5,5,5,5,5,6,6,7 };
110     printf("First Equal           : %2d 
", searchFirstEqual(arr, 16, 5));
111     printf("Last Equal            : %2d 
", searchLastEqual(arr, 16, 5));
112     printf("First Equal or Larger : %2d 
", searchFirstEqualOrLarger(arr, 16, 5));
113     printf("First Larger          : %2d 
", searchFirstLarger(arr, 16, 5));
114     printf("Last Equal or Smaller : %2d 
", searchLastEqualOrSmaller(arr, 16, 5));
115     printf("Last Smaller          : %2d 
", searchLastSmaller(arr, 16, 5));
116     system("pause");
117     return 0;
118 }
119 
120 /*输出:
121 First Equal           :  3
122 Last Equal            : 12
123 First Equal or Larger :  3
124 First Larger          : 13
125 Last Equal or Smaller : 12
126 Last Smaller          :  2
127 */

参考博客(感谢~):

【1】:http://blog.csdn.net/yefengzhichen/article/details/52372407

【2】:https://61mon.com/index.php/archives/187/

【3】:https://github.com/julycoding/The-Art-Of-Programming-By-July/blob/master/ebook/zh/04.01.md#分析与解法

【4】:http://www.cnblogs.com/luoxn28/p/5767571.html

【5】:http://www.cnblogs.com/bofengyu/p/6761389.html

【6】:http://blog.chinaunix.net/uid-1844931-id-3337784.html

【7】:https://www.zhihu.com/question/36132386

【8】:http://www.ahathinking.com/archives/179.html

原文地址:https://www.cnblogs.com/zxhyxiao/p/7426878.html