1. 程式人生 > >二分查找小結

二分查找小結

大於等於 sea tail 不同 html 算法 printf mes net

  在弄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

二分查找小結