Java算法之位运算入门

1.数字在计算机中的表示

首先理解几个概念:

  • 原码:正负数的原码是符号位加上该数绝对值的二进制表示,例如(以八bit为例),1的原码是符号位0加上1的二进制0000001,-3的原码是符号位1加上3的二进制0000011,我们得到:1的原码为00000001,-3的原码是10000011
  • 反码:正数的反码和原码相同,负数的反码是在其原码基础上,符号位不变,其余各位取反。比如-3的反码是:11111100
  • 补码:正数的补码和原码相同,负数的补码是其反码+1,比如-3的补码是:11111101
  • ***  内存中用补码表示一个数。***

2.位运算规则

1)与运算&:a&b,对于a,b两数的每个二进制位,当两数对应的位都为1时,结果才为1,否则为0

0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1

2)或运算| :a|b,对于a,b两数的每个二进制位,当两数对应的位有一个为1,结果就为1,否则为0

0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1

3)异或运算^: a^b, 对于a,b两数的每个二进制位,两数对应的位不同,结果就为1,相同就为0

0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0

4) 取反运算~:~a,对于数a的每个二进制位进行取反操作,1变0,0变1

~0 = 1
~1 = 0

注意某个数进行位运算时的二进制表示,是补码形式表示***

5)移位运算:java中移位运算有三种:

        (1)<< : 左移运算,将全部二进制位向左移动若干位,高位丢弃,低位补0

        (2)>> : 算术右移,将全部二进制位向右移动若干位,低位丢弃,高位补最高位

        (3)>>> : 逻辑右移,将全部二进制位向右移动若干位,低位丢弃,高位补0

注:何为高位补最高位?就是如果原来数的最高位(符号位)是0,算术右移后,高位补位时就全部补0;如果原来数的最高位(符号位)是1,算术右移后,高位补位时就全部补1。

例如-50的二进制表示是11001110,算术右移两位(-50)>> 2 就是 □□110011,方框中补原来最高位1,就是11110011,即-13。注意这里的二进制表示都是补码。而-50逻辑右移两位(-50)>>> 2就是方框中补0,00110011,即51

3.位运算中的重要技巧

首先必须记住如下几个公式:(以下的1s,0s表示与x等长的一串1或一串0):

不难理解,因为&运算时只要某一位为0,结果位就为0,所以x&0s = 0;|运算时只要某一位为1,结果位就为1,所以x | 1s = 1,其他两个可以自己推一推。

这四个公式都特别有用,能帮助我们在运用的时候选择究竟是用&运算还是|运算。

比如,我们现在要把二进制数的某一位变成0,其他位不变,那应该怎么做?我们能看出,其他位不变(即结果位仍为x)可以选择x & 1或者 x | 0,但是要把某一位变成0的话,显然只能用&0的方式。所以我们得出,选择&运算而不是|运算。即,如果要把二进制数的第i位变成0,就应该&一个数,这个数的第i位是0,其他位是1.

同样的道理,如果要把二进制数的某一位变成1,其他位不变呢?其他位不变可以选择x & 1或者 x | 0,但是要把一位变成1就只能用 | 1的方式,所以选择|运算而不是&运算。即,要把二进制数的第i位变成1,我们应该|一个数,这个数的第i位是1,其余位是0(这样就能x | 0 = x,从而使其余位不变)。

具体实现如下:

1)实现将某数的一个二进制位变成1或变成0

/**
     *
     * @param num 待修改的数
     * @param i 将该数的从右往左数第i位设置为1
     * @return 返回修改后的数
     */
    public static int setBit1(int num , int i){
        //设置为1用|1,保持不变用|0,所以应该|一个数,该数的第i位为1,其他位为0
        //怎样得到这个数呢?1的二进制是00000...01,所以把1左移(i-1)位.
        //因为低位补0,就能得到只有第i位为1的数
        return num | (1 << (i-1));
    }
/**
     * 把数num的二进制从右往左数第i位设置为0,返回得到的数
     */
    public static int setBit0(int num ,int i){
        //思路,设置为0应该用&0,所以选用&运算,其他各位保持不变,故其他位&1
        //所以要找到一个第i位为0,其他位都为1的数来和num进行&运算
        //1111..01111,这样的数怎么找?依旧是把1左移i-1位得到0000...10000..,然后按位取反得到1111...01111
        return num & (~(1 << (i-1)));
    }

2)实现获取某数的某个二进制位

如何获取一个数的某个二进制位呢?我们要通过某种方式把这个二进制位单独拿出来考虑。因此有一个思路是这样的:除了要获取的二进制位,该数的其他位都清零。这样如果要获取的二进制位是1,结果就不为0,如果要获取的二进制位是0,结果就为0.

代码如下:

 /**
     * 获取数num的第i个二进制位,返回1或者0
     */
    public static int getBit(int num,int i){
        //把第i位以外的其他位都清零,第i位不变。
        //要清零就是&0,所以用&,保持不变就用&1,这样就是得到一个只有第i位为1的数做&运算
        //如果结果为0代表第i位为0,否则为1
        return (num & (1 << (i-1))) == 0 ? 0 : 1;
    }

在上面的代码中可以发现,前面这四个公式运用的是很频繁的,掌握它们能解决很多问题,所以别的位运算公式可以不记住,这四个公式请务必牢记:

最后补充一个位运算偶尔会用到的巧妙公式:

a&(a-1)的结果为:将a的二进制表示的最后一个1变成0

这个公式的推导牵扯到另外两个公式:x&x = x ;  x|x = x,即一个数和自己做& 和 | 运算,结果还是自己。有了这个基础我们再来看上面的公式,a-1的效果是把a从右往左数的第一个1变成0,举个栗子:a的二进制是00011100,那么a-1就要借位,结果是00011011,可以看到,第一个1变成了0,1之前的0变成了1。所以a&(a-1)中,因为(a-1)中从右往左数第一个1变为0,而&0的结果肯定是0,所以结果位中,第一个1所在的位置变为0。其他位要么是a&a,要么是a&1s,结果都为a本身,所以a&(a-1)的结果中,除了a的从右往左数第一个1变为0之外,其他位都不变。

这是一个偶尔会用到的公式,大家不理解的话,只需要记住上面图片中的四个主要公式,这是重中之重。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值