编程珠玑Column9中二分查找的优化

二分查找的确是个很不错的算法,虽然简单,但是边界容易出错。

书中最初的二分查找l和u(我的代码中是r)对应数组中最小下标和最大下标,即l=0,u=n-1,数组为arr[l..u]。而后面优化的代码中l和u就变成了最小下标-1和最大下标+1了,即l=-1,u=n,数组为arr[l+1...u-1]。不太明白其中深刻含义,我试了一下l=0,u=n(Python中范围都是这么表示),发现第二段代码(即找出t的第一处位置)实现不出来,用原来的代码在数组只剩下1个或2个元素的时候会出现死循环。

所以,我索性用最原始的表示方法实现了后面几个优化代码,并做了个时间测试程序(脚手架)来看运行时间。

原二分查找:

原二分查找
 1 int bsearch(int *arr,int t,int l,int r)

 2 {

 3     int m;

 4     while(l<=r)//要探测的部分至少有1个元素,l==r的时候仅有1个元素

 5     {

 6         m=(l+r)/2;

 7         if(t>arr[m])

 8             l=m+1;

 9         else if(t<arr[m])

10             r=m-1;

11         else

12             return m;//发现t后马上返回结果

13     }

14     return -1;

15 }

改进的二分查找(找出t的在数组中的最小下标)

二分查找1
 1 int bsearch1(int *arr,int t,int l,int r)

 2 {

 3     int m;

 4     while(l<r)//要探测的部分至少有2个元素,必须要l<r而不是l<=r,否则会无限循环

 5     {

 6         m=(l+r)/2;

 7         if(t>arr[m])

 8             l=m+1;

 9         else

10             r=m;

11     }

12     if(arr[l]==t)//检查最后剩余的1个元素

13         return l;

14     else

15         return -1;

16 }

用l和i表示范围的二分查找(同样是找出t的在数组中的最小下标)

二分查找2
 1 int bsearch2(int *arr,int t,int l,int r)//已知数组有1000个元素

 2 {

 3     int i=512;

 4     //根据下面的判断,将数组范围变成arr[0..511]或arr[488..999]

 5     //长度均为512(2的整数次幂)

 6     if(t>=arr[512])

 7         l=1000-512;

 8     while(i>1)//探测部分仅剩1个元素的时候停止,想想前一步:探测部分仅有2个元素

 9     {

10         i/=2;

11         if(t>arr[l+i-1])//l+i-1相当于m(下取整的中点)

12             l+=i;

13     }

14     if(arr[l]==t)//检查最后剩余的1个元素

15         return l;

16     else

17         return -1;

18 }

上一个函数循环展开(同样是找出t的在数组中的最小下标)

二分查找3
 1 int bsearch3(int *arr,int t,int l,int r)

 2 {

 3     if(t>=arr[512])

 4         l=1000-512;

 5     if(t>arr[l+255]) l+=256;

 6     if(t>arr[l+127]) l+=128;

 7     if(t>arr[l+63]) l+=64;

 8     if(t>arr[l+31]) l+=32;

 9     if(t>arr[l+15]) l+=16;

10     if(t>arr[l+7]) l+=8;

11     if(t>arr[l+3]) l+=4;

12     if(t>arr[l+1]) l+=2;

13     if(t>arr[l]) l+=1;

14     if(t==arr[l])

15         return l;

16     else

17         return -1;

18 }

时间测试程序

time test
 1 int main()

 2 {

 3     int arr[1000];

 4     for(int i=0;i<1000;++i)

 5         arr[i]=i;

 6     int (*func[])(int *,int,int,int)={bsearch,bsearch1,bsearch2,bsearch3};//函数数组

 7     for(int i=0;i<4;++i)

 8     {

 9         clock_t begin=clock();

10         int count=5000;

11         while(count--)

12             for(int t=-1;t<=1000;++t)

13             {

14                 int res=func[i](arr,t,0,999);

15                 if(t==1000&&res!=-1||t!=1000&&res!=t)

16                     cout<<i<<" "<<t<<endl;

17             }

18         clock_t use=clock()-begin;

19         cout<<"bsearch"<<i<<" total clock:"<<use<<endl;

20     }

21     return 0;

22 }

测试结果显示速度上:bsearch3>bsearch2>bsearch>bsearch1。

但是这个测试结果并不具有普适性,因为速度较快的bsearch3和bsearch2都已知数组长度为1000,而最初的bsearch没有这个限制。

于是,我想通过修改程序让bsearch2也具有普适性。最初修改的版本

修改的bsearch2
 1 int bsearch2(int *arr,int t,int l,int r)

 2 {

 3     int i;//下面的循环和之后的移位,目的是找到不大于数组长度的最大的2的次幂

 4     for(i=1;r-l+1>=i;i<<=1);

 5     i>>=1;

 6     if(t>=arr[i])

 7         l=r-l+1-i;

 8     while(i>1)//探测部分仅剩1个元素的时候停止,想想前一步:探测部分仅有2个元素

 9     {

10         i/=2;

11         if(t>arr[l+i-1])//l+i-1相当于m(下取整的中点)

12             l+=i;

13     }

14     if(arr[l]==t)//检查最后剩余的1个元素

15         return l;

16     else

17         return -1;

18 }

修改之后测试速度:bsearch3>bsearch>bsearch1>bsearch2。

尼玛,变成最慢的了。。。

问题的症结应该是如何更高效的找到不大于n(数组长度)的2的最大次幂。网上找到一个不错的方法http://www.cnblogs.com/chaosz/archive/2012/09/02.html

它是找不小于a的2的最小次幂。我在它的基础上稍作修改

improved
 1 int bsearch2(int *arr,int t,int l,int r)

 2 {

 3     int i=r-l+1;

 4     i|=i>>1;

 5     i|=i>>2;

 6     i|=i>>4;

 7     i|=i>>8;

 8     i|=i>>16;

 9     i=(i>>1)+1;

10     if(t>=arr[i])

11         l=r+1-i;

12     while(i>1)//探测部分仅剩1个元素的时候停止,想想前一步:探测部分仅有2个元素

13     {

14         i/=2;

15         if(t>arr[l+i-1])//l+i-1相当于m(下取整的中点)

16             l+=i;

17     }

18     if(arr[l]==t)//检查最后剩余的1个元素

19         return l;

20     else

21         return -1;

22 }

修改之后测试速度:bsearch3>bsearch>bsearch2>bsearch1。

看来普适之后,bsearch2对阵不过bsearch。

综上,在[1]数组大小不确定,[2]不要求找到t的下标最小的位置,的条件下,还是原bsearch速度最快、最好理解。

你可能感兴趣的:(column)