一些碰上的bit运算


今天看到了一段代码,具体是要动态分配一大小不固定的数据分配内存,并且要求这段内存的大小还必需是2^k,所以实际分配的大小就等于把计算出来的数据大小向上取到最接近它的一个2次幂。
这段代码做了如下操作:

1 a--;
2 a |= (a >> 1);
3 a |= (a >> 2);
4 a |= (a >> 4);
5 a |= (a >> 8);
6 a |= (a >> 16);
7 a++;

因为从前没见过,就琢磨了一下原理:我们知道一个数的2进制形式类似于

image
那么比这个数大并且最接近这个数的2次幂就是2^(k+1),其中k就是最接近MSB(最高有效位,即左起第一位)的那一位1.
而求2^(k+1)就可以通过把最高位1之后的位都置1,即这个数转化成000001111111111,然后在加1得到。

把最高位的1向低位扩散的最简单的方法就是,连续k次每次把这个数左移一位跟结果“与”。但是更快的方法就是利用之前移动的结果,把已经扩散到的一起位去跟剩下的位“与”,这样每次扩散的位数都是之前的2倍,在log(k)次可以完成,上述代码就是这么操作的,过程如下图。

上面的处理都是针对a不是2的幂次方的情况,所以最开始的a--操作是为了a恰好等于2的k次幂的情况转化为不等,以便同一处理。

image

利用bit运算的特性可以给不少问题提供有意思的解决问题,比如说编程之美上那道求1的个数的经典题目,就是利用了2^k & (2^k - 1) = 0的性质(因为2^k = 10..00,而2^k - 1的二进制形式为01..11),每次把最靠近lsb(最低有效位,即右起第一位)的那个1消掉。这个方法稍做变动也能解决当前的问题:
记录a&(a-1)等于0时a的数值,然后左移1位即可,当然为了避免恰好遇上2^k的情况,也要先进行减1操作。

与上述问题相似,操作系统中常用到的一个宏align也采用了bit操作,align(p, b)用于对齐地址p,其中b是要对齐的字节数,它必须是2的指数幂
p = (p + b - 1) & ~(b - 1)

如果p事先不是对齐的,那它就会被转化为一个比它的对齐地址要大的一个数align(p,b)+s,其中s<b,然后

bitcount问题还有一个并行解法,这个方法的特点是节约指令,并且可以扩展到更多位的情况下,它主要利用的一个性质是:
对于一个2bit数t,t中1的个数恰好等于t-(t>>1),如下表
image 
因此该算法的第一步
t = t - (t>>1) & 0x55555555
就是先以每两个bit为单位求出1的个数。
接下来第二步
t = t & 0x33333333 + (t >> 2) & 0x33333333
把前面求出来的每两bit的1的个数两两相邻累加,从而求出以每4bit为单位1的个数。
接下的3、4、5步同第二步一样,分别求出每8、16直到32bit中1的个数
t = (t  + (t >> 4)) & 0x0f0f0f0f
t = (t  + (t >> 8)) & 0x00ff00ff
t = (t  + (t >> 16)) & 0x0000ffff
所不同的是它们把与项提到了外面,而第2步里则不能这样做,因为每四位的1的个数至最多占用3位,进位会导致计算结果不正确

还有一个性质被用在了树状数组里面:

获得t的最小的非0后缀bit串可以通过t & (-t)得到,即能整除t的最大2的幂次方。

因为-t相当于t按位变反加1,结果就是t中除了最右边的1以及之后的0,剩下的bit全被取反了,再于t“与”就只剩下了未被取反的位,也就是原来t中最右边的1以及之后的0。

说明bit运算强大的一个例子就是8皇后问题,代码简单明了,速度快,占用空间小

 1 /* 8皇后问题的bit运算解法 */
 2 int size = 8;
 3 int sum = 0;
 4 upperlim = 1 << size - 1;
 5 /* 参数: @row 代表当前行在列限制下的空闲位置,空闲位置所对应的bit为0,下同 
 6  *    @ld left diagnal 当前行上在左对角线限制下的空闲位置
 7  *    @rd right diagnal 当前行上在右对角线限制下的空闲位置
 8  */
 9 void test(int row, int ld, int rd)
10 {
11     if(row != uppderlimit) {
12         /* 求出当前行上可用的位置 */
13         int pos = upperlimit & ~(row | rd | ld);
14         while(pos) {
15             int p = pos & (-pos);
16             pos = pos - p;
17             /*尝试一个可用位置p, 并且更新行限制,左对角线限制,右对角线限制,继续测试下一行*/
18             test(row + p, (ld + p) >> 1, (rd + p) >> 1);
19         }
20     } else 
21         sum = 0; /* 列都填满了,说明找到了一个解 */
22 }

转载于:https://www.cnblogs.com/chaosz/archive/2012/09/02/2667809.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值