位1的个数(二)

1.每三位储存

1.前言

我们要知道每三位储存和每两位储存以及每四位储存不一样的地方在于哪里 2和4这种数字都可以被32整除 但是3不行 意味着如果你一开始使用的是每三位储存的方式的话 那么就不可以将32位二进制位进行平均分组 但是实际上也行 就是把多余出来的二进制位统统设置为0即可 

那么这样的话 我们就可以将一个int类型的数据划分成11组3位的 你其实可以将32位二进制位看成33位的 然后将多余出来的二进制位统统看成0即可 这样仍然可以实现均分操作 又不改变原来的数据

然后由于我们采取的是每三位一组储存的方式 所以我们在按位与的时候 按位与对象采取八进制的形式更加妥当 因为一个八进制对应三个二进制 刚好是一组 然后按位与对象中多余出来的的位统统设置为0 确保这个按位与对象不会发生数值溢出现象(因为int类型数据中多余出来的位都是0 不管谁按位与0 其结果都是0 所以统一将按位与对象多余出来的二进制位设置为0 你还不能设置为1 因为怕数值溢出 并且整型默认是int类型 所以也就只能设置为0了) 并且统一设置为33位

2.加法运算

总体的思路就是截取每一组中的每一位 然后进行加和操作 最后得到的结果就是每一组中1的个数

我们先采取先位移后按位与的操作 按位与对象是0b001001001001001001001001001001001 对应的八进制是011111111111 演示代码如下所示:

public static int bitCount(int n){
    // 一开始采取的是每三位储存的方式
    n = (n & 011111111111) + ((n >>> 1) & 011111111111) + ((n >>> 2) & 011111111111);
}

那么如果我们采取的是先按位与后位移的方式的话 那么演示代码如下所示:

public static int bitCount(int n){
    // 一开始每三位储存 加法运算的思路是:截取每一组中的每一位 然后进行加和操作
    // 如果你想要截取第一位的话 那么让其余两位消失即可 二进制中按位与0即可让对应位置的二进制位消失 如果按位与1则可以让对应位置的二进制位进行保留 
    n = (n & 011111111111) + ((n & 022222222222) >>> 1) + ((n & 044444444444) >>> 2);
}

3.减法运算

减法运算的原理在昨天的位1的个数有讲过 这边就不重复讲了

首先我们采取的是先位移后按位与的操作 演示代码如下所示:

public static int bitCount(int n){
    // 一开始采用的是每三位储存的方式 然后采用的是减法运算 并且是先位移后按位与的操作
    n = n - ((n >>> 1) & 033333333333) - ((n >>> 2) & 011111111111);
}

其次如果我们采用的是先按位与后位移的方式的话 那么演示代码如下所示:

public static int bitCount(int n){
    // 一开始采取的是每三位储存 并且是减法运算的方式 然后是先按位与后位移的操作
    n = n - ((n & 066666666666) >>> 1) - ((n & 044444444444) >>> 2);
}

4.合并组别

每六位一组

public static int bitCount(int n){
    // 一开始是每三位储存 然后是加法运算 并且是先按位与后位移的操作
    n = (n & 011111111111) + ((n & 022222222222) >>> 1) + ((n & 044444444444) >>> 2);
    // 每六位储存 相当于将32位看成36位 然后分成6组6位的 只不过原来32位多余出来的的位看成为0即可
    n = (n & 0070707070707) + ((n & 0707070707070) >>> 3);
}

每十二位一组

public static int bitCount(int n){
    // 一开始是每三位储存 然后是加法运算 并且是先按位与后位移的操作
    n = (n & 011111111111) + ((n & 022222222222) >>> 1) + ((n & 044444444444) >>> 2);
    // 每六位储存 相当于将32位看成36位 然后分成6组6位的 只不过原来32位多余出来的的位看成为0即可
    n = (n & 0070707070707) + ((n & 0707070707070) >>> 3);
    // 每十二位储存 相当于将32位看成36位 然后分成3组12位的 
    n = (n & 0007700770077) + ((n & 0770077007700) >>> 6);
}

每二十四位一组

public static int bitCount(int n){
    // 一开始是每三位储存 然后是加法运算 并且是先按位与后位移的操作
    n = (n & 011111111111) + ((n & 022222222222) >>> 1) + ((n & 044444444444) >>> 2);
    // 每六位储存 相当于将32位看成36位 然后分成6组6位的 只不过原来32位多余出来的的位看成为0即可
    n = (n & 0070707070707) + ((n & 0707070707070) >>> 3);
    // 每十二位储存 相当于将32位看成36位 然后分成3组12位的 
    n = (n & 0007700770077) + ((n & 0770077007700) >>> 6);
    // 每二十四位储存 相当于将32位看成48位 然后分成2组24位的
    n = (n & 00000777700007777) + ((n & 07777000077770000) >>> 12);
}

每四十八为一组(最后一组)

public static int bitCount(int n){
    // 一开始是每三位储存 然后是加法运算 并且是先按位与后位移的操作
    n = (n & 011111111111) + ((n & 022222222222) >>> 1) + ((n & 044444444444) >>> 2);
    // 每六位储存 相当于将32位看成36位 然后分成6组6位的 只不过原来32位多余出来的的位看成为0即可
    n = (n & 0070707070707) + ((n & 0707070707070) >>> 3);
    // 每十二位储存 相当于将32位看成36位 然后分成3组12位的 
    n = (n & 0007700770077) + ((n & 0770077007700) >>> 6);
    // 每二十四位储存 相当于将32位看成48位 然后分成2组24位的
    n = (n & 00000777700007777) + ((n & 07777000077770000) >>> 12);
    // 每四十八为一组 相当于将32位看成96位 然后分成2组48位的
    n = (n & 000000000777777770000000077777777) + ((n & 077777777000000007777777700000000) >>> 24);
    return n;
}

但是你仔细看一下上面的代码 整数类型默认是int类型 按位与的对象从044444444444开始都超出了int类型的范围 所以直接就数值溢出了 更别说后续的0070707070707以及其他更大的数了

其实原因就在于你怎么去处理你的按位与对象 为了让你的按位与对象不出现数值溢出现象 我们需要将多余出来的二进制位统统设置为0(因为多余出来的二进制位都是0 不管是1还是0按位与0 其结果都是0) 就可以避免这个问题了 然后我们可以将按位与对象统统设置为33位二进制位 也就是11位八进制位 这样看起来形式比较统一 而且写法不会越来越复杂

更新后的版本如下所示:

public static int bitCount(int n){
    // 一开始是每三位储存 然后是加法运算 并且是先按位与后位移的操作
    n = (n & 011111111111) + ((n & 022222222222) >>> 1) + ((n & 004444444444) >>> 2);
    // 每六位储存 相当于将32位看成36位 然后分成6组6位的 只不过原来32位多余出来的的位看成为0即可
    n = (n & 030707070707) + ((n & 007070707070) >>> 3);
    // 每十二位储存 相当于将32位看成36位 然后分成3组12位的 
    n = (n & 007700770077) + ((n & 030077007700) >>> 6);
    // 每二十四位储存 相当于将32位看成48位 然后分成2组24位的
    n = (n & 037700007777) + ((n & 000077770000) >>> 12);
    // 每四十八为一组 相当于将32位看成96位 然后分成2组48位的
    n = (n & 000077777777) + ((n & 037700000000) >>> 24);
    return n;
}

5.可以改进的地方

对于上述版本 我们其实存在改进的地方 比如:当我们执行到12位一组的时候 我们就可以开始去掉按位与操作 然后等到最后在进行一次总体的过滤 实际代码如下所示:

public static int bitCount(int n){
    // 一开始是每三位储存 然后是加法运算 并且是先按位与后位移的操作
    n = (n & 011111111111) + ((n & 022222222222) >>> 1) + ((n & 004444444444) >>> 2);
    // 每六位储存 相当于将32位看成36位 然后分成6组6位的 只不过原来32位多余出来的的位看成为0即可
    n = (n & 030707070707) + ((n & 007070707070) >>> 3);
    // 每十二位储存
    n = n + (n >>> 6);
    // 每二十四位储存
    n = n + (n >>> 12);
    // 每四十八位储存
    n = n + (n >>> 24);
    return n & 63;
}

除了说以上这种改进方法 我们其实还可以尝试另外一种改进方法 就是等到每六位储存完毕以后 直接将每六位一组储存的1加起来 然后进行过滤 但是这个时机得选对

一个int类型的数据最多最多也就只可能有32个1 在二进制中最多就用6个二进制位去储存 所以说我们如果在执行玩每三位储存以后就开始进行每三位一组中1的个数的加和操作的话 那么最终结果很有可能被低6位中的高3位中的多余的1导致出错 所以说就算有多余的1未经过滤 我们也需要保证低6位没有其他多余的1影响 因此我们需要得到每一组的个数至少为6个才行 所以说代码如下所示:

public static int bitCount(int n){
    // 一开始是每三位储存 然后是加法运算 并且是先按位与后位移的操作
    n = (n & 011111111111) + ((n & 022222222222) >>> 1) + ((n & 004444444444) >>> 2);
    // 每六位储存 相当于将32位看成36位 然后分成6组6位的 只不过原来32位多余出来的的位看成为0即可
    n = (n & 030707070707) + ((n & 007070707070) >>> 3);
    return ((n + (n >>> 6) + (n >>> 12) + (n >>> 18) + (n >>> 24) + (n >>> 30)) & 63);
}

至此我们就将每三位储存的解决方式也讲了 其实这种不能平均分组的情况还使用与每五位储存、每七位储存等不能被32整除的情况 原理都一样的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

axihaihai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值