“二分查找”——Jon Bentley:90%以上的程序员无法正确无误的写出

来源:互联网 发布:mac桌面时钟 编辑:程序博客网 时间:2024/05/23 19:13
#include<iostream> #include<iomanip>  void print(int i,int j,int a[]);int find(int a[],int n,int k);int find(int a[],int n,int k){int high=n-1,low=0,mid;//初始化,注意n为数组长度while (low<=high){//mid=(low+high)/2;当n较大时(low+high)会溢出,应将式子等价代换成下面的式子mid=low+(high-low)/2;//或mid=low+(high-low)>>1std::cout<<"low="<<low<<"  high="<<high;std::cout<<"  (low+high)/2="<<std::setiosflags(std::ios::fixed)<<std::setw(4)<<std::setprecision(1);std::cout<<(low*1.0+high)/2<<"  mid="<<mid<<"  a["<<mid<<"]="<<a[mid]<<std::endl<<std::endl;//显示程序中间变量,理清程序过程。(low*1.0+high)/2是为了输出mid的小数值if(k==a[mid])return mid;else if(k<a[mid]){high=mid-1;print(low,high,a); //显示程序中间变量,理清程序过程}else{low=mid+1;print(low,high,a); //显示程序中间变量,理清程序过程}}return -1;}void print(int i,int j,int a[])//输出{while(i<=j){std::cout<<a[i]<<"   ";i++;}std::cout<<std::endl;}int main(){int a[]={10,11,12,13,14,15,16,17,18,19};print(0,9,a);std::cout<<"k所在数组下标"<<find(a,10,15)<<std::endl;return 0;}

 1>二分查找可以解决(预排序数组的查找)问题:只要数组中包含T(即要查找的值),那么通过不断缩小包含T的范围,最终就可以找到它。一开始,范围覆盖整个数组。将数组的中间项与T进行比较,可以排除一半元素,范围缩小一半。就这样反复比较,反复缩小范围,最终就会在数组中找到T,或者确定原以为T所在的范围实际为空。对于包含N个元素表,整个查找过程大约要经过log(2)N次比较。

2>代码中要注意的地方是:

(1)语句 mid=low+(high-low)/2 要放入while循环中实现对mid的更新

(2)while循环的终止条件是low<=high而不是low<high因为当区间只有一个元素时low==high==mid,如果少了=号,此情况下的最后一次循环丢  失,在VC2005下运行上述程序就将令 k=13,可以看到运行结果的倒数第三行显示" low=3  high=3  (low+high)/2=3  mid=3  a[3]=13 "即为此种情况

(3) 语句mid=(low+high)/2是容易忽略的一个bug,即当n值较大时lowhigh的大小都很容易就可能接近n,这时溢出造成mid为负值,数组越界

(4)查找范围的缩小是由语句high=mid-1;和low=mid+1;实现的

3>唯一可能还有疑惑的是,在范围的缩小过程中为什么不会遗漏,其实这一点不用怀疑。从程序while循环的框架中我们可以知道,程序执行流程可能有三个去向(1)k==a[mid];(2) k<a[mid];  (3)k>a[mid]; 因为数组a[]是升序的,所以在每一次while循环体执行时,k都会落入相应区域内,而不必要确保mid是否恰好为下标正中间值,即不需要关心左右区域的大小是否相同,查找过程不可能遗漏,也不可能重复。最终要查找的值被找到的时候,一定是k==a[mid],只是k==a[mid]可能是在两种情况下产生的(1)  low<high;       (2)low==high;  在VC2005下运行上述程序,分别令k=15和k=13,则对应这两种情况。

对数组中有重复值的情况的考虑:二分查找主要用于对已排序的无重复的数组的查找,当某些特殊情况下,数组中的值有重复使用二分查找时,所找到的只是其中一个目标值的位置。但我们可以知道其余值在其左右,只需在其左右逐个搜索直到不等于目标值即可