二分查找及其几个变形
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <iostream> 2 #include <cassert> 3 4 using namespace std; 5 6 //给定一个非降序有序数组,返回出现value的任意 7 //一位置,不存在则返回-1 8 //low:a中第一个元素的下标 9 //high:a中最后一个元素的下标 10 //如果在a中有多个value,每次返回的都是value第一次 11 //出现的下标 12 int BinarySearch1(int *a,int low,int high,int value) 13 { 14 if (!a || low>high || value<a[low] || value>a[high]) 15 { 16 return -1; 17 } 18 int middle=low; 19 low--; 20 high++; 21 //low--最初是为了对a中只有两个元素时也能进入while循环 22 //但是low--之后,middle=low+((high-low)>>1);计算得到值 23 //并不是数组a的中间位置的值,为了平衡low--,所以让high++ 24 //这样计算得到的就是数组的中间位置的值了。high++的另外一个 25 //好处就是当a中只有一个元素时,while循环也是满足的。 26 while(low+1!=high) 27 { 28 middle=low+((high-low)>>1); 29 //由于value>a[middle]的条件约束,如果找到value,那么 30 //对应的下标肯定在high端,low端所对应的元素值都比value小。 31 if (value>a[middle]) 32 { 33 low=middle; 34 } 35 else 36 { 37 high=middle; 38 } 39 } 40 //由于low+1!=high的条件约束,当high和low之间只存在两个元素时, 41 //此时low对应第一个元素,high对应第二个元素,不会进入while循环 42 //也就不能对high元素是否等于value进行判断。所以需要下面的if判断 43 //a[high]!=value。当a中的所有元素都不要查找的元素小时 44 if (a[high]!=value) 45 { 46 high=-1; 47 } 48 return high; 49 } 50 51 52 //给定一个非降序有序数组,返回出现value的最后 53 //一个位置,不存在则返回-1 54 int BinarySearch2(int *a,int low,int high,int value) 55 { 56 if (!a || low>high || value<a[low] || value>a[high]) 57 { 58 return -1; 59 } 60 int middle=low; 61 low--; 62 high++; 63 while(low+1!=high) 64 { 65 middle=low+((high-low)>>1); 66 //由于value<a[middle]的条件约束,如果找到value,那么 67 //对应的下标肯定在low端,high端所对应的元素值都比value大。 68 if (value<a[middle]) 69 { 70 high=middle; 71 } 72 else 73 { 74 low=middle; 75 } 76 } 77 //这里要对a[low]进行探测 78 if (a[low]!=value) 79 { 80 low=-1; 81 } 82 return low; 83 } 84 85 //辅助方法:二分查找 86 //输出最小的i,使得a[i]大于value. 87 //如果所有a[i]均小于value,则返回 88 //high+1。如果出现错误返回-1 89 int BinarySearch3(int* a,int low,int high,int value) 90 { 91 //边界条件检查 92 if (!a || low>high) 93 { 94 return -1; 95 } 96 //下面两个if语句在while循环中都会处理这两种情况 97 //之所以单独提出来,是因为这两种情况无需进入while 98 //循环,直接用if语句就可以处理了。 99 //if (value>elem[high]) 100 //{ 101 // return high+1; 102 //} 103 //if (value<elem[low]) 104 //{ 105 // return low; 106 //} 107 //这里之所以令low减1是为了这种情况考虑: 108 //当high-low=1时,即只有两个元素,那么 109 //while循环就不会满足,所以令low减1。 110 low--; 111 high++; 112 int middle; 113 //这里如果high的值没有改变,说明a中的所有数字都比 114 //value大,那出了循环直接返回high就可以了。这个时候 115 //不需要像查找value在a中是否存在那样,while执行之后 116 //使用if条件判断。这里不要if判断了。 117 while (low+1!=high) 118 { 119 middle=low+((high-low)>>1); 120 if (a[middle]>value) 121 { 122 high=middle; 123 } 124 else 125 { 126 low=middle; 127 } 128 } 129 return high; 130 } 131 132 133 //辅助方法:二分查找 134 //输出最大的i,使得a[i]小于value. 135 //如果所有a[i]均大于value,则返回 136 //low-1。如果出现错误返回-2 137 //因为low-1可能等于-1,所以这里用-2 138 //作为错误码 139 int BinarySearch4(int* a,int low,int high,int value) 140 { 141 //边界条件检查 142 if (!a || low>high) 143 { 144 return -2; 145 } 146 //下面两个if语句在while循环中都会处理这两种情况 147 //之所以单独提出来,是因为这两种情况无需进入while 148 //循环,直接用if语句就可以处理了。 149 //if (value>elem[high]) 150 //{ 151 // return high; 152 //} 153 //if (value<elem[low]) 154 //{ 155 // return low-1; 156 //} 157 low--; 158 high++; 159 int middle; 160 //这里如果high的值没有改变,说明a中的所有数字都比 161 //value大,那出了循环直接返回high就可以了。这个时候 162 //不需要像查找value在a中是否存在那样,while执行之后 163 //使用if条件判断。这里不要if判断了。 164 while (low+1!=high) 165 { 166 middle=low+((high-low)>>1); 167 if (a[middle]<value) 168 { 169 low=middle; 170 } 171 else 172 { 173 high=middle; 174 } 175 } 176 return low; 177 } 178 179 //给定一个非降序有序数组,输出最接近value 180 //的两个值的下标 181 pair<int,int> BinarySearch5(int *a,int low,int high,int value) 182 { 183 //边界条件检查 184 if (!a || low>high) 185 { 186 return pair<int,int>(-2,-2); 187 } 188 //这里之所以令low减1是为了这种情况考虑: 189 //当high-low=1时,即只有两个元素,那么 190 //while循环就不会满足,所以令low减1。 191 low--; 192 high++; 193 int middle; 194 //这里如果high的值没有改变,说明a中的所有数字都比 195 //value大,那出了循环直接返回high就可以了。这个时候 196 //不需要像查找value在a中是否存在那样,while执行之后 197 //使用if条件判断。这里不要if判断了。 198 while (low+1!=high) 199 { 200 middle=low+((high-low)>>1); 201 if (a[middle]<value) 202 { 203 low=middle; 204 } 205 else 206 { 207 high=middle; 208 } 209 } 210 pair<int,int> pos(high,high); 211 if (a[high]!=value) 212 { 213 pos.first=low; 214 } 215 return pos; 216 } 217 218 219 int main() 220 { 221 const int aLength=7; 222 int a1[aLength]={1,2,3,4,5,6,8}; 223 int a2[aLength]={1,1,1,1,1,1,1}; 224 int a3[aLength]={1,1,2,2,2,3,3}; 225 int a4[aLength]={1,2,3,3,4,4,5}; 226 cout<<"返回value第一次出现的位置:"<<endl; 227 for (int i=0;i<aLength;i++) 228 { 229 cout<<BinarySearch1(a1,0,aLength-1,a1[i])<<" "; 230 } 231 cout<<endl; 232 for (int i=0;i<aLength;i++) 233 { 234 cout<<BinarySearch1(a2,0,aLength-1,a2[i])<<" "; 235 } 236 cout<<endl; 237 for (int i=0;i<aLength;i++) 238 { 239 cout<<BinarySearch1(a3,0,aLength-1,a3[i])<<" "; 240 } 241 cout<<endl; 242 for (int i=0;i<aLength;i++) 243 { 244 cout<<BinarySearch1(a4,0,aLength-1,a4[i])<<" "; 245 } 246 cout<<endl; 247 cout<<BinarySearch1(a1,0,aLength-1,7)<<endl; 248 249 cout<<"返回value最后一次出现的位置:"<<endl; 250 for (int i=0;i<aLength;i++) 251 { 252 cout<<BinarySearch2(a1,0,aLength-1,a1[i])<<" "; 253 } 254 cout<<endl; 255 for (int i=0;i<aLength;i++) 256 { 257 cout<<BinarySearch2(a2,0,aLength-1,a2[i])<<" "; 258 } 259 cout<<endl; 260 for (int i=0;i<aLength;i++) 261 { 262 cout<<BinarySearch2(a3,0,aLength-1,a3[i])<<" "; 263 } 264 cout<<endl; 265 for (int i=0;i<aLength;i++) 266 { 267 cout<<BinarySearch2(a4,0,aLength-1,a4[i])<<" "; 268 } 269 cout<<endl; 270 cout<<BinarySearch2(a1,0,aLength-1,7)<<endl; 271 272 cout<<"返回值大于value的最小的下标:"<<endl; 273 for (int i=0;i<aLength;i++) 274 { 275 cout<<BinarySearch3(a1,0,aLength-1,a1[i])<<" "; 276 } 277 cout<<endl; 278 for (int i=0;i<aLength;i++) 279 { 280 cout<<BinarySearch3(a2,0,aLength-1,a2[i])<<" "; 281 } 282 cout<<endl; 283 for (int i=0;i<aLength;i++) 284 { 285 cout<<BinarySearch3(a3,0,aLength-1,a3[i])<<" "; 286 } 287 cout<<endl; 288 for (int i=0;i<aLength;i++) 289 { 290 cout<<BinarySearch3(a4,0,aLength-1,a4[i])<<" "; 291 } 292 cout<<endl; 293 cout<<BinarySearch3(a1,0,aLength-1,7)<<endl; 294 295 cout<<"返回值小于value的最大的下标:"<<endl; 296 for (int i=0;i<aLength;i++) 297 { 298 cout<<BinarySearch4(a1,0,aLength-1,a1[i])<<" "; 299 } 300 cout<<endl; 301 for (int i=0;i<aLength;i++) 302 { 303 cout<<BinarySearch4(a2,0,aLength-1,a2[i])<<" "; 304 } 305 cout<<endl; 306 for (int i=0;i<aLength;i++) 307 { 308 cout<<BinarySearch4(a3,0,aLength-1,a3[i])<<" "; 309 } 310 cout<<endl; 311 for (int i=0;i<aLength;i++) 312 { 313 cout<<BinarySearch4(a4,0,aLength-1,a4[i])<<" "; 314 } 315 cout<<endl; 316 cout<<BinarySearch4(a1,0,aLength-1,7)<<endl; 317 318 cout<<"返回值最接近value的两个下标,如果存在value就两个值同时为value的下标:"<<endl; 319 pair<int,int> pos; 320 for (int i=0;i<aLength;i++) 321 { 322 pos=BinarySearch5(a1,0,aLength-1,a1[i]); 323 cout<<"("<<pos.first<<","<<pos.second<<")"<<" "; 324 } 325 cout<<endl; 326 for (int i=0;i<aLength;i++) 327 { 328 pos=BinarySearch5(a2,0,aLength-1,a2[i]); 329 cout<<"("<<pos.first<<","<<pos.second<<")"<<" "; 330 } 331 cout<<endl; 332 for (int i=0;i<aLength;i++) 333 { 334 pos=BinarySearch5(a3,0,aLength-1,a3[i]); 335 cout<<"("<<pos.first<<","<<pos.second<<")"<<" "; 336 } 337 cout<<endl; 338 for (int i=0;i<aLength;i++) 339 { 340 pos=BinarySearch5(a4,0,aLength-1,a4[i]); 341 cout<<"("<<pos.first<<","<<pos.second<<")"<<" "; 342 } 343 cout<<endl; 344 pos=BinarySearch5(a1,0,aLength-1,7); 345 cout<<"("<<pos.first<<","<<pos.second<<")"<<" "; 346 int a5[aLength]={1,5,9,9,13,20,25}; 347 pos=BinarySearch5(a5,0,aLength-1,0); 348 cout<<"("<<pos.first<<","<<pos.second<<")"<<" "; 349 pos=BinarySearch5(a5,0,aLength-1,4); 350 cout<<"("<<pos.first<<","<<pos.second<<")"<<" "; 351 pos=BinarySearch5(a5,0,aLength-1,7); 352 cout<<"("<<pos.first<<","<<pos.second<<")"<<" "; 353 pos=BinarySearch5(a5,0,aLength-1,9); 354 cout<<"("<<pos.first<<","<<pos.second<<")"<<" "; 355 pos=BinarySearch5(a5,0,aLength-1,28); 356 cout<<"("<<pos.first<<","<<pos.second<<")"<<" "; 357 cout<<endl; 358 }
《编程之美》3.11介绍了二分查找,并给出了几个思考问题,上面的代码对几个思考问题给出了解答:
在上述代码中需要注意的几点问题:
1. 为了避免整数的溢出问题,需要写出:middle=low+((high-low)>>1); 最好不要写成middle=(low+high)>>1;
2. 二分代码最需要考虑的就是边界问题,边界也是最容易出错的地方。
3. 上述代码有很多技巧及细节的处理,这也是结合《编程珠玑》第九章给出的编码。
4. 在while循环判断使用low+1!=high而不是使用low<=high,是因为使用后者很难找出最接近value的数。有可能middle最接近value,而每次更新是high=middle+1,low=middle-1,这样就把middle跳过去了。个人见解,欢迎拍砖。
5. 写一个正确高效的二分真得不容易!!!
6. 运行结果如下: