博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一些二进制问题的巧妙方法
阅读量:6815 次
发布时间:2019-06-26

本文共 2702 字,大约阅读时间需要 9 分钟。

hot3.png

问题一、判断二进制位是否只有一个1

    这个问题也就是求判断一个数是否是2的整数次幂。

    对于这个问题,看起来的第一感觉就是循环判断每一位,当然,这也是最简单的算法。

bool flag = false;bool result = true;while(number > 0){    if(number & 1)        if(!flag)            flag = true;        else            result = false;            //该数二进制位有多个1    number >>= 1;}

    显然,上述算法是O(n)的,n为二进制位数,怎么把算法降到O(1)呢,这就要用的巧妙的位运算了。直接上代码:

bool result = number & (number - 1);

    本来写了说明,但是这么简单的相信都看得懂,所以删去了。。。。

    

问题二、求二进制位里有多少个1

    相对于第一个问题,这个问题就稍稍有点复杂了,当然,是针对低复杂度的解法。

       最水的方法当然是对每一位就行1的判断,只需要稍微改一下问题一的解法,就得到了:

int sum = 0;while(number > 0){    sum += (number & 1);    number >>= 1;}

    显然,这个方法也是O(n)的,但是,当我们见识过问题一的巧妙解法后,可以尝试对这个O(n)的解法做一下优化:

int sum = 0;while(number > 0){    number &= (number - 1);    sum ++;}

    上面已经说过,number & (number - 1)每次消除最右边的那个1,所有,一直消下去,当number等于0时,进行了多少次运算,也就有多少个1.

    这个算法的复杂度当然不及O(n)但是,当number全为1的最坏情况下,也就达到O(n)了,那么,还有比这个更优的解法吗?当然,还可以采取空间换时间的方法。

    例如,利用arr[] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4}这个数组,每次判断4个二进制位,然后通过数组找到对于的1的个数。例如判断10100011,即A3h,arr[10] + arr[3] 就是4。

    这一类通过空间换时间的方法,复杂度根据判断的位数而定,但是就复杂度而言,还是属于O(n)范畴的。还能再降低复杂度吗?答案是能:

    例如对于16位的数字:

number = (number & 0x5555) + ((number >> 1) & 0x5555);number = (number & 0x3333) + ((number >> 2) & 0x3333);number = (number & 0x0F0F) + ((number >> 4) & 0x0F0F);number = (number & 0x00FF) + ((number >> 8) & 0x00FF);

    5次运算后,number的值就是原number二进制位中1的个数。

    先举个例子吧,number = abcd 1111 1111 1111;(a、b、c、d = 0 or  1)

    在这里,我们这样看待上面的每一位数:每一个数代表该位上有几个1。那么,1代表该位上有1个1,0代表有0个1.

    number & 0x5555 = 0b0d 0101 0101 0101;

    (number >> 1) & 0x5555 = 0a0c 0101 0101 0101;

    现在a代表第一位的1的个数,b代表第二位1的个数,两者相加,则a+b的和代表了前两位上1的个数,c+d代表了第三、四位上1的个数。例如a = b = 1,则a+b=10 即 2,所以,第一次运算后,number= a+b c+d 1010 1010 1010  ,a+b,c+d各占两个二进制位。第二次运算:

    number & 0x3333 = 00c+d 0010 0010 0010;

    (number >> 1) & 0x3333= 00a+b 0010 0010 0010;

    同理,两者相加,a+b+c+d代表了前4位上1的个数,新的number = a+b+c+d 0100 0100 0100;后面3个0100=4,即原来这4位上有4个1。再往下去,就是a+b+c+d  +  0100得到前8位的1的个数,以此类推,就可以得到number所有位的1的个数了。

    可以看出,该算法的复杂度是O(logn)的,可以说是最少的了。

问题三、求二进制最右边的1是第几位

    现在,有了前两个问题的铺垫,第三个问题也就没有那么复杂了。

    首先,小学生解法略。

    接下来,针对32位整数,采用空间换时间的方法。声明一个数组

int arr[32] = {	0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,		31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9};

    那么,这个问题的解法就是:

int answer = arr[ ((number & -number) * 0x077CB531 ) >> 27 ];

    咋一看,几个数莫名其妙,下面就来解释一下。

    number & -number 保留最右边的一个1,将其他位的1置0,这样以后,就只有32种结果了。即2^i (i = 0,1....31),剩下的问题,就是如何建立2^i (i = 0,1....31) 和 {1,2.....32}这两个集合的双射(一一映射和满射)关系了。将这32个上述结果 * 0x077CB531后,得到的数,最高5个二进制位没有完全一样的,正好利用这一点,建立满射关系(其实就是hash)。得到的数右移27位,就是为了获得最高的5位。5位二进制数也就是0~31的范围,代入数组,就能获得最终答案。

    0x077CB531 和 数组怎么得到的呢? 可以通过遍历1  ~  2^32-1,测试每一个数,与2^i (i = 0,1....31)相乘后,看得到的32个数,最高5个二进制位组成的数a是不是唯一的。如果是唯一的,再通过a和2^i (i = 0,1....31)建立hash数组。int32范围内,这样的数可以找到4096个。

转载于:https://my.oschina.net/u/2248183/blog/523776

你可能感兴趣的文章
李彦宏:人工智能的互联网时代已经到来
查看>>
游标概念和作用(转载)
查看>>
python中全局变量、局部变量、类变量、实例变量简析
查看>>
大众公布量子计算北京交通新一代产品亮相
查看>>
武器加持无人机,远程操控就可以抓获犯罪团伙
查看>>
MySQL数据库迁移
查看>>
IOS应用提交所需的ICON
查看>>
第90届中国电子展聚焦行业新热点,拉动产业链上下游快速发展
查看>>
量子力学多世界解释:这个世界的你是穷光蛋 另一个世界是亿万富翁(文中有赠书活动)...
查看>>
不要小看了互联网智能锁,它正撬动整个多元化居住产品时代!
查看>>
工人小明的新同事
查看>>
OPC UA的安全性分析以及正确使用指南
查看>>
使用树莓派和 projectx/os 托管你自己的电子邮件
查看>>
关于nmonanalyser报错“输入超出文件尾”的解决方法
查看>>
轻松面试找到理想员工-非官方的面试技术指南
查看>>
当主库发生宕机,从库如何接管主库
查看>>
卷影副本(Shadow Copies)
查看>>
重新回归
查看>>
AngularJs 知识
查看>>
Spring.NET的AOP怎么玩
查看>>