二分搜索:
是在数组为有序的前提下,查找某个具体的元素是否在数组中的方法,每次迭代使得查找的范围减少一半,从而在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都不错,这里就不多写了。
842

被折叠的 条评论
为什么被折叠?



