二分搜索知多少

二分搜索:

              是在数组为有序的前提下,查找某个具体的元素是否在数组中的方法,每次迭代使得查找的范围减少一半,从而在O(logn)的时间内找出查找完成.

              怎么样呢?是不是感觉很简单,没有什么难以理解的地方,是啊,的确用语言描述确实没什么,但是别忘了,我们在面试或者日常工作中需要实实在在的写在纸上或者是IDE中,这个时候我们突然发现自己真正写的时候多么的拙计.

             下面是《编程珠玑》上面对二分搜索在没有任何特殊的约束下的程序代码(特殊的要求比如说:找出第一个或者是最好一次出现的位置)

              

begin=0;end=n-1;
loop :
   if begin>end
      p=-1;break
   m=begin+(end-begin)>>1;
  case
   x[m]<t:begin=m+1
   x[m]==t:p=m;break;
   x[m]>t:end=m-1
  
          通过断言我们确实可以验证上述算法的正确性.算法的循环不变量为:begin<=end;并且a[begin]<=t<=a[end];

         下面是作者对二分搜索提出的特殊的要求:找出排序数组中出现t的第一个位置:

         作者给出了四种算法:

算法1:

begin=-1;end=n;
while begin+1!=end
         m=begin+(end-begin)>>1;
         if x[m]<t
            begin=m;
         else
            end=m;
p=end;
    if p>=n||x[p]!=t
        p=-1;
   return p;

作者给出的循环不变式为begin<end以及x[begin]<t<=x[end];

并且我们可以验证在循环的过程中该不变式确实得到保持:假设x[-1]<t<=x[n](因为这两个元素确实不存在,其值我们可以任意的假设,从而保证了不变式初始条件的成立),

在每次的循环中:m为begin和end的中点。只所以采用begin+(end-begin)>>1,而不是(begin+end)/2,是为了防止两者偏大导致越界从而m的值出现错误。假设x[m]<t.

则赋值begin=m。首先保证了begin<end;因为begin+1<end;从而两者之间的差值至少为2.也就是说m的值肯定会比end小.这个时候x[begin]=x[m]<t<=x[end]循环不变式得到满足。

反之若是x[m]>=t则end=m,利用和上面相同的分析知begin<end;同时x[begin]<t<=x[m]=x[end];这个时候循环不变式得到满足,而且这个循环不会出现死循环的问题,原因在于我们的循环的结束

条件为begin+1==end.而这个条件随着begin或者是end的被赋值会最终得到满足.

当循环结束的时候,我们需要知道begin+1==end;同时满足x[end]>=t>x[begin],所以若是存在的话则只能为end,最后的两行就是对end是否满足要求的判断

算法二:

        算法二采用了不同的范围表示法,不再使用begin和end来表示上下限值,而是使用下限值begin以及增量i来表示end=begin+i;程序的代码将保证i为2的幂次.这里存在的问题在于我们首先需要找到比

数组元素小的最大的2的幂次.算法如下表示:      

   i=512;//假设数组元素为1000
begin=-1;
if x[511]<t
     begin=1000-512
while i!=1
     nexti=i/2;
     if x[begin+nexti]<t
          begin=begin+nexti;
          i=nexti;;
    else
         i=nexti;
p=begin+1;
if p>1000||x[p]!=t
   p=-1
return p;  
需要稍微说明的地方是:为什么在开始之前如果x[511]<t;这个时候需要将begin设置为1000-512,我们知道需要保持这样的循环不变式:x[begin]<t<=x[begin+i];

那么为什么不将begin设置为511呢?原因在于我们需要保证end-begin==i,而i必须为2的幂次。这个时候时候既然x[511]满足条件,那么当下标小于511的肯定也满足条件,同时为了保证下标的差值为512,从而使用了

1000-512;

关于上述算法的循环不变式和算法一是一样的。同样关于循环不变式的证明和算法一的证明也没啥区别

作者又给出了算法三和算法四。算法三是对算法二的优化。而算法四是将i所有可能的值均列出来,省去了循环,但是这样子我觉得算法的可扩展性不是很强,这里就不再写了。

课后习题:

          给定一个非常长的字节序列:如何高速地统计1的个数呢?(也就是在整个序列中有多少位的值为1)

         作者给出了两种方法:

                  第一种:计算每个输入单元(可能是一个8位的字符或者是32位的整数)中为1的位数。然后将其相加。为了查找出一个输入单元中包含的1的位数,我们可以采用b&=b-1.因为这样子是把从右面数第一位的1变位0,该位右面的

所有的0变位1,其余位保持不变,这样子再与原来的值求按位与运算,则将原来为1的位变位了0,该位左边的所有的位保持不变,然后我们只需要判断这个时候的b是否为0就行了。或者是我们可以采用查表的方法:0到65535中每个数包含的

1的位数都是可以直接算出来的,只需要建立一个65536大小的数组,数组元素为相应下标包含的1的位数的多少

              第二种:计算输入单元中每个输入单元的个数,然后将这个数乘以相应输入单元中为1的位数,最后再对各个输入单元求总和.

              感觉方法二就是对输入的元素进行分类,确定每类的多少,然后再乘以每类包含1的多少就行了

      习题8和习题12都不错,这里就不多写了。


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值