《编程之美》读书笔记(二)——二进制数中“1”的个数

题目下面一段话直接点出了考察的目的,最求效率(时间复杂度)最优。

题目思路由直观到巧妙【狡猾】。 我第一想到的是第二种位移的方法,反而第一种方法除法不会想。

这里要注意的是,八字节无符号整形变量的表示方式,数据类型用的是BYTE,切记不要直接把BYTE赋值到int,因为int是有符号整数,在赋值过程中,如果BYTE最高位是1会被理解成负数,赋值到int类型后就变成负数,并且以补码形式存储。

第三种方法就比较巧妙了,直接从”1″开始入手,思路是通过某种方式把1逐个消掉,统计需要几次才能把所有“1”消掉,只剩下全为“0”。从简单只有一个”1″的例子开始思考,发现(v&v-1)即00100和00011“与”操作可以消除掉“1”。

第四种方法和第五种方法更”狡猾”,提前算好,写死在那,直接查就行了。这让我联想到ipv4路由表查找算法,ipv4(32bit)是LPM,也有按位查找的算法和查表算法两类,但是到了ipv6(128bit)就不能查表了。

扩展问题1. 如果是32位,需要从存储空间成本、查询效率要求、查询频率等方面考虑,查询频率主要是考虑到查表法是需要提前把所有可能的输入都算一遍,然后记录下来,如果查询的输入样本来来去去是那么几个,那就可能不需要提前写好所有表项,而是表项最初留空,当查表时发现表项为空则使用前面三种方法之一进行计算,并记录在表中。

扩展问题2. 等价于求 (A XOR B)串中“1”的个数。

书中最后推荐的网址(
http://blog.csdn.net/justpub/article/details/2292823)里有对算法的分析,提供了新的想法:      小的规模(8bit, 32bit)下,复杂度其实是比较次要的因素。算法一固定循环8次,也是O(1)。      规模小,衡量尺度也应该小,可以考虑用CPU时钟周期作为衡量。           算法1中用到加法,整数除法,循环分支判断,奇偶性判断,【一般一个branch penalty消耗14个左右的cycle】,加起来大概几十个cycle。           查表法问题在于需要用到访存操作【访问数组】,第一次访问的时候数组很有可能不在cache中,cache miss导致需要几十或者上百个cycle去访问内存。

网站中推荐了两个算法,第一个是分治法【尼玛,网上说个毛二分法!】,思路是: f[31,0] = f[31,16] + f[15,0] = (f[31,24]+f[23,16]) + … f[n,n] = bit[n] = 0 or 1 具体实现用的位操作,自底向上运算,计算结果放在原地:

x = (x & 0x55555555) + (x>>1 & 0x55555555) //计算结果占2位
x = (x & 0x33333333) + (x>>2 & 0x33333333) //计算结果占4位
x = (x & 0x0f0f0f0f) + (x>>4 & 0x0f0f0f0f) //计算结果占8位
x = (x & 0x00ff00ff) + (x>>8 & 0x00ff00ff) //计算结果占16位
x = (x & 0x0000ffff) + (x>>16 & 0x0000ffff) //计算结果占32位

网上的代码是进行优化变形以后的结果。 HAKMEM代码没看懂。

网上又找到一个算法集锦,非常的全。 (
http://www.cnblogs.com/graphics/archive/2010/06/21/1752421.html)  其中介绍了个完美法,太惊艳了,就两行代码

int BitCount5(unsigned int n)
{
    unsigned int tmp = n - ((n >> 1) & 033333333333) - ((n >> 2) & 011111111111);
    return ((tmp + (tmp >> 3)) & 030707070707) % 63;
}

注:
以0开头的是8进制数,以0x开头的是十六进制数,上面代码中使用了三个8进制数。 1)用八进制表示的数abc,用十进制表示则成4a+2b+c,其中a/b/c取值为0或1 因为 (4a+2b+c)>>1 = 2a+b, (4a+2b+c)>>2 = a a+b+c = (4a+2b+c) – (2a+b) – a = (4a+2b+c) – (4a+2b+c)>>1 – (4a+2b+c)>>2; 第一行代码就是求出每三位中“1”的个数,存在原地3比特中。 2)然后,相邻两个3bit组左右相加,加到右边3比特(tmp + tmp>>3),然后用&030707070707来屏蔽左边的3比特,得到的是以6个bit为一组的统计“1”个数。 3)最后,需要把各个6bit组的数值相加。这里用到了取模63,解释如下:2)得到的结果可十进制地表示为 a*64^3 + b*64^2 + c*64 + d【举例】,要求a+b+c+d 利用了 (a*b) mod c = ((a mod c ) * (b mod c)) mod c和(a+b) mod c = ((a mod c ) * (b mod c)) mod c,因此,      (a*64^3 + b*64^2 + c*64 + d) mod 63    =(a%63 * 64%63 * 64%63 * 64%63 + b%63 * 64%63 * 64%63 + c%63 * 64%63 * 64%63 + d%63) % 63    =(a%63 + b%63 + c%63 + d%63) % 63 因为a+b+c+d均小于63,因此上式等于a+b+c+d

    原文作者:210在吾道
    原文地址: https://blog.csdn.net/u011514747/article/details/9496701
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞