二分查找小结
在弄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 \n", searchFirstEqual(arr, 16, 5)); 111 printf("Last Equal : %2d \n", searchLastEqual(arr, 16, 5)); 112 printf("First Equal or Larger : %2d \n", searchFirstEqualOrLarger(arr, 16, 5)); 113 printf("First Larger : %2d \n", searchFirstLarger(arr, 16, 5)); 114 printf("Last Equal or Smaller : %2d \n", searchLastEqualOrSmaller(arr, 16, 5)); 115 printf("Last Smaller : %2d \n", 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