二分查找小結
在弄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
二分查找小結