编程珠玑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速度最快、最好理解。

转载于:https://www.cnblogs.com/particle/archive/2013/03/14/2960513.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值