一道简单的算法题

题目:统计给定数字中,值为1的二进制位的数量。如果是数组呢?

解法1:遍历算法

int getBitCount(unsigned int num) {
    int count = 0;
    
    while(num) {
        if(num & 0x01)
            count++;
        
        num = num >> 1;
    }
    
    return count;
} 

第一种想法比较简单,从最后一位开始,比较是否为1,如果为1,就计数器加一。循环次数固定,32次。但是这种方法有一个地方需要注意,那就形参必须为unsigned int。否则,如果num为负数时,此时右移为了保证移位后还是负数,最高位会一直置为1,那将陷入死循环。

解法2:遍历算法(改进)

int getBitCount2(unsigned int num) {
    int count = 0;
    
    while(num) {
        count++;
        
        num = num & (num - 1);
    }
    
    return count;
} 

相比较第一种算法,我们不必每次判断一位,而是通过num & (num-1)来判断。每次&之后,可以把num最低位的1置为0,那么循环的次数,也就降低到了所含1的个数。

解法3:查表法

static const unsigned char bitsinbyte[256] = {
    //0000 0000 - 0000 0001
    0,1,
    
    //0000 0010 - 0000 0011
    1,2,
    
    //0000 0100 - 0000 0111
    1,2,2,3,
    
    //0000 1000 - 0000 1111
    1,2,2,3,2,3,3,4,
    
    //0001 0000 - 0001 1111
    1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
    
    //0010 0000 - 0011 1111
    1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
    2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
    
    //0100 0000 - 0111 1111
    1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
    2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
    2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
    3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
    
    //1000 0000 - 1111 1111
    1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
    2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
    2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
    3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
    2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
    3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
    3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
    4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8
};
int getBitCount3(unsigned int num) {
    unsigned char n1 = num;
    unsigned char n2 = num >> 8;
    unsigned char n3 = num >> 16;
    unsigned char n4 = num >> 24;
    
    return bitsinbyte[n1] + bitsinbyte[n2] +
        bitsinbyte[n3] + bitsinbyte[n4];
} 

通过定义一个bitsinbyte[256]字节数组,里面存放0000 0000 - 1111 1111不同数字的1的个数。然后把需要统计的数字划分为四段,然后分部计算。

解法4:variable-precision SWAR算法

unsigned int getBitCount4(unsigned int num) {
    num = (num & 0x55555555) + ((num >> 1) & 0x55555555);
    
    num = (num & 0x33333333) + ((num >> 2) & 0x33333333);
    
    num = (num & 0x0F0F0F0F) + ((num >> 4) & 0x0F0F0F0F);
    
    num = (num * 0x01010101 >> 24);
    
    return num;
} 

这种算法也常被称为汉明重量(Hamming Weight),通过一系列的位移和位运算操作,可以在常数时间内计算多个字节的汉明重量,而且不需要使用额外的内存。接下来分析以下这个算法。

为了方便描述,我们假定一个字节0xD8 ->(二进制) 0B11011000从后往前,依次为1到8位,第一位为0,第八位为1。

  • step1:首先我们可以很容易的知道,0x55555555对应的二进制的数为0B 01010101 01010101 01010101 01010101,而第一步运算相当于,把num奇偶位的数字进行相加。并且存放在了奇数位,相加如有进位则放在偶数位。

  • step2:0x33333333对应的二进制的数为0B 00110011 00110011 00110011 00110011,把num的奇数位,与下一个奇数位相加(第一位加第三位,第五位加第七位),把num的偶数位,与下一个偶数位相加(第二位加第四位,第六位加第八位)。如有进位,则保存到第三位,或者第七位。

  • step3:0x0F0F0F0F对应的二进制的数为0B 00001111 00001111 00001111 00001111,把num的每个字节中,前四位,与后四位相加。此时,每个字节中所含1的个数,都集中到了前四位。此时可以用0x0m0n0i0j来表示这个数,其中m,n,i,j代表之前num每个字节所含1的个数。

  • step4:也是最神奇的一步,通过这一步,把m,n,i,j这四个数相加。得到最终的个数。在这一步,我们不需要把0x01010101化为二进制。而是直接带入运算。通过下面的计算式,我们可以看出相乘,然后右移24位,刚好就是我们所要的结果。神奇~

magic.png

运算速度

|算法(ms)/数据级别| 10| 10^2| 10^3| 10^4| 10^5| 10^6| 10^7| 10^8|
| :----:| :----: | :----: | :----: | :----: | :----: | :----: | :----: |
|遍历|0 |0 |1 |2 |26| 255 |2700 |29447|
|遍历(改进)|0 |0 |0 |1 |7 |74 |739 |8046|
|查表|0 |0 |0 |0 |2 |21 |202 |2166|
|SWAR|0 |0 |0 |0 |2 |19 |190 |1876|

以上是模拟不同的数据级别,运行测试的结果。


参考资料:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 我们每周有技术分享会,今天有道算法跟大家分享下。 我说说我知道的三种思路 先剔除N,再找出最大值 这种方式要for...
    慢慢爬的小蜗牛stone阅读 310评论 0 0
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,493评论 18 399
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,798评论 6 13
  • 越来越不了解自己了,最近的两天一直在反问自己,为什么要将写作划进自己自己的生活圈,在时间有限的情况下,选两件自己...
    风居住的街道233阅读 307评论 0 0
  • 年前,一同学因婚变而被折磨得十分潦倒,不料这家伙耗上了我,成天如祥林嫂般在我耳边絮叨,直到把我的情绪也泡得...
    顾鸣GM阅读 225评论 0 1