二分查找算法在C/C++程序中的应用示例
二分查找算法的思想很简单,《编程珠玑》中的描述: 在一个包含t的数组内,二分查找通过对范围的跟综来解决问题。开始时,范围就是整个数组。通过将范围中间的元素与t比较并丢弃一半范围,范围就被缩小。这个过程一直持续,直到在t被发现,或者那个能够包含t的范围已成为空。 int binarysearch1(int a[],int n,int x) { int l,u,m; l=0;u=n; while(l<u) { m=l+((u-l)>>1); if(x<a[m]) u=m; else if(x==a[m]) return m; else l=m+1; } return -1; } 这里注意一点,由于使用的是不对称区间,所以下标的调整看上去有点不规整。一个是u=m,另一个是l=m+1。其实很好理解,调整前区间的形式应该是 [ )的形式,如果中间值比查找值小,那么调整的是左边界,也就是闭的部分,所以加1;否则,调整是右边界,是开的部分,所以不用减1。调整后仍是[ )的形式。当然也可以写成对称的形式。代码如下: int binarysearch1(int a[],m; l=0;u=n-1; while(l<=u) { m=l+((u-l)>>1); if(x<a[m]) u=m-1; else if(x==a[m]) return m; else l=m+1; } return -1; } 这样也看上去比较规整,但是有个不足。如果想把程序改成“纯指针”的形式,就会有麻烦。修改成纯指针的代码如下: int binarysearch2(int *a,int x) { int *l,*u,*m; l=a;u=a+n-1; while(l<=u) { m=l+((u-l)>>1); if(x<*m) u=m-1; else if(x==*m) return m-a; else l=m+1; } return -1; } 当n为0时,会引用无效地址。而用非对称区间则不会有这个问题。代码如下: int binarysearch2(int *a,*m; l=a;u=a+n; while(l<u) { m=l+((u-l)>>1); if(x<*m) u=m; else if(x==*m) return m-a; else l=m+1; } return -1; } 上面给出的二分查找是迭代法实现,当然也可以用递归的方式实现。代码如下: int binarysearch3(int a[],int l,int u,int x) int m=l+((u-l)>>1); if(l<=u) { if(x<a[m]) return binarysearch3(a,l,m-1,x); else if(x==a[m]) return m; else return binarysearch3(a,m+1,x); } return -1; int binarysearch4(int a[],m; int flag=-1; l=0;u=n; while(l<u) { m=l+((u-l)>>1); if(x<a[m]) u=m; else if(x==a[m]) flag=u=m; else l=m+1; } return flag; } 下面是《编程珠玑》上的解法: int binarysearch4(int a[],m; l=-1;u=n; while(l+1<u) { m=l+((u-l)>>1); if(a[m]<x) l=m; else u=m; } return (u>=n||a[u]!=x)?-1:u; } #include <iostream> #include <cassert> #include <algorithm> #include <ctime> using namespace std; int calmid(int l,int u) { return l+((u-l)>>1); } int binarysearch1(int a[],int x); #define bs1 binarysearch1 int main() { long start,end; start=clock(); int a[9]={-2147483648,-13,-10,-5,-3,1,400,2147483647}; //中值下标计算的测试 assert(calmid(0,1)==0); assert(calmid(0,2)==1); assert(calmid(1000000,2000000)==1500000); assert(calmid(2147483646,2147483647)==2147483646); assert(calmid(2147483645,2147483647)==2147483646); //冒烟测试 assert(bs1(a,9,0)==5); assert(bs1(a,1)==6); assert(bs1(a,2)==-1); //边界测试 assert(bs1(a,1)==-1); //0个元素 assert(bs1(a,-2147483648)==0); //1个元素 成功 assert(bs1(a,-2147483647)==-1); //1个元素 失败 assert(bs1(a,-2147483648)==0); //首个元素 assert(bs1(a,-3)==4); //中间元素 assert(bs1(a,2147483647)==8); //末尾元素 //自动化测试 int b[10000]; int i,j; for(i=0;i<10000;i++) { b[i]=i*10; for(j=0;j<=i;j++) { assert(bs1(b,i+1,j*10)==j); assert(bs1(b,j*10-5)==-1); } } //自动化测试 引入随机数 srand(time(0)); for(i=0;i<10000;i++) { b[i]=rand()%1000000; sort(&b[0],&b[i]); for(j=0;j<=i;j++) { int x=rand(); int k=bs1(b,x); if(k!=-1) assert(b[k]==x); } } end=clock(); cout<<(end-start)/1000.0<<'s'<<endl; return 0; } 注意到数组的元素有正数,负数,零,最大值,最小值。通常会忘掉负数的测试,引入最大值和最小值,主要是为了边界测试。 Note:二分查找容易忽略的一个bug //a为排好序的数组,n为数组的大小,x为指定元素 int binarySearch(int a[],int x) { int left = 0,right = n-1,middle = 0; int tmp = 0; while(left <= right) { middle = (left + right)/2; tmp = a[middle]; if(x < tmp) right = middle - 1; else if(x > tmp) left = middle + 1; else return middle; } return -1; } 乍看没有错误,但是不幸的是,该程序存在一个bug。当数组极大时,(left+right)可能为负数,则数组下标溢出,程序崩溃。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |