美文网首页
Java位运算

Java位运算

作者: Heezier | 来源:发表于2020-08-06 11:18 被阅读0次

    前言

    先抛出一个问题?

    题目来源:https://www.zhihu.com/question/19676641/answer/12613290

    有 1000 个一模一样的瓶子,其中有 999 瓶是普通的水,有一瓶是毒药。任何喝下毒药的生物都会在一星期之后死亡。现在,你只有 10 只小白鼠和一星期的时间,如何检验出哪个瓶子里有毒药?

    如果用二分法:

    1000 / 2 = 500

    500 / 2 = 250

    250 /2 = 125

    125 / 2 = 62 + 63

    63 / 2 = 31 + 32

    到现在位置你已经用完了所有的小白鼠,你只能确定32or31中的一堆,显然是无法实现了。

    可以用位运算来思考,该问题。

    1,把1000瓶标号:1,2,3,4,5,6...1000.
    2,所有老鼠排列在一起组成一个2进制队列: 0000000000
    0代表不喝,1代表喝
    3,0000000001代表第一瓶水被喝情况
    0000000010代表第二瓶水被喝情况
    0000000011代表第三瓶水被喝情况
    0000000100代表第四瓶水被喝情况
    ...
    1111101000代表第1000瓶水被喝情况
    4,第7天,喝了毒药的老鼠都死了,那个二进制队列转为为十进制就是毒药的标号。
    比如第3只老鼠死亡,其他老鼠没死,队列为0000000100,第四瓶水有毒。
    第1,5,6,8老鼠死亡,其他没死,队列为0010110001,第177瓶水有毒。

    作者:kirch
    链接:https://www.zhihu.com/question/19676641/answer/14123096
    来源:知乎
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    日常开发中位运算不是很常用,但是巧妙的使用位运算可以大量减少运行开销,优化算法。举个例子,翻转操作比较常见,比如初始值为1,操作一次变为0,再操作一次变为1。可能的做法是使用三木运算符,判断原始值为1还是0,如果是1,设置为0,否则设置为0.但是使用位运算,不用判断原始值,直接改变值就可以:1^num,

    java支持的位运算符

    1、原码、反码和补码说明:

    一个数可以分成符号位(0正1负)+ 真值,原码是我们正常想法写出来的二进制。由于计算机只能做加法,负数用单纯的二进制原码书写会出错,于是规定了反码(正数不变,负数符号位不变,真值部分取反);再后来由于+0, -0的争端,于是改进反码,变成补码(正数不变,负数符号位不变,真值部分取反,然后+1)。二进制前面的0都可以省略,所以总结来说:计算机里的负数都是用补码(符号位1,真值部分取反+1)表示的。

    2、位运算符和2的关系

    位运算符和乘2、除2在大多数时候是很相似的,可以进行替代,同时效率也会高的多,但是两者切记不能混淆 ;很多时候有人会把两者的概念混淆,尤其是数据刚好是 2、4、6、8、100等偶数的时候,看起来就更相似了,但是对于奇数,如本文使用的12345 ,右移之后结果为6172 ,这个结果就和数学意义上的除以2不同了,不过对于int 类型的数据,除2 会对结果进行取整,所以结果也是6172 ,这就更有迷惑性了。

    按位与(&)

    按位与的运算规则

    操作数1 0 0 1 1
    操作数2 0 1 0 1
    按位与 0 0 0 1

    规则总结:只有两个操作数对应位同为1时,结果为1,其余全为0. (或者是只要有一个操作数为0,结果就为0)。

    按位或(|)

    按位或的运算规则

    操作数1 0 0 1 1
    操作数2 0 1 0 1
    按位或 0 1 1 1

    规则总结:只有两个操作数对应位同为0时,结果为0,其余全为1.(或者是只要有一个操作数为1,结果就为1)

    按位非(~)

    按位非的运算规则

    操作数 0 1
    按位或 1 0

    按位异或(^)

    按位异或的运算规则

    操作数1 0 0 1 1
    操作数2 0 1 0 1
    按位异或 0 1 1 0

    规则总结:异:1

    关于异或运算符,有很多非常有用的特性,我们在这里梳理总结一下。

    Ⅰ、异或运算符满足交换律

    也就是说,ab与ba是等价的,虽然a和b交换了位置,但还是会运算出相同的结果。这个规律还可以推广到N个操作数,也就是说,如果有N个变量都参与了异或运算,那么它们的位置无论如何交换,运算的结果都是相同的。

    Ⅱ、任何两个相同的数字进行异或操作,所得到的结果都必然为0

    这个特性并不难理解,因为两个相同的数字,换算成补码后,每个二进制位上的数也都相同,这样在进行异或运算时,按照运算规则,每个二进制位上得到的运算结果也都是0,这N个0所组成的二进制串就是数字0的补码。我们可以利用这个特性快速的判断两个整数是否相同。另外,利用这个特性还可以实现内存的快速清零操作,比如我们可以在代码中写上a=a^a;这条语句能快速的把变量a所占据的那几个字节的内存迅速清零。

    Ⅲ、对于任意一个二进制位来说,这个位上的数与0进行异或运算,运算结果与这个二进制位上的数是相同的,而与1进行异或运算,结果与这个二进制位上的数字相反

    注意,我们现在说的是二进制位上的数字,所谓相反不是说原来这个位上是1,运算结果是-1,而是说原来是1,运算结果为0,原来如果是0,运算结果是1,这才是此处所说的”相反”的概念。这个特性也非常好理解,小伙伴们一定要记住它,在以后进行一些位运算操作的时候经常会用到这个特性。

    Ⅳ、对于任何两个整数a和b,abb等于a

    这个结论为什么成立呢?简单说来,就是因为这个表达式中有bb,而bb的结果为0,前文已讲过,任何一个数与0进行按位异或操作,结果仍然是这个数本身,所以,abb等于a。这个特性在加密运算方面有着很普遍的应用。我们可以把a当作要加密的数据,而把b当作密钥。a异或b就是把a用密钥b进行了加密操作,当需要解密时,仍然以b作为密钥,再进行一次异或就实现了解密。

    这个特性还可以推出另外一个结论:对于任何两个整数a和b,aba等于b。我们能够得到这个结论的原因也很简单,就是因为按照交换律,ab与ba的运算结果是一样的,所以aba等价于baa,这个表达式中出现了aa,aa的值也为0,所以整个表达式的其实就相当于b^0,最终结果还是b。

    左移(<<)

    运算规则:按二进制形式把所有的数字向左移动对应的位数,高位移出(舍弃),低位的空位补零。例如: 12345 << 1,则是将数字12345左移1位:

    img

    位移后十进制数值变成:24690,刚好是12345的二倍,所以有些人会用左位移运算符代替乘2的操作,但是这并不代表是真的就是乘以2,很多时候,我们可以这样使用,但是一定要知道,位移运算符很多时候可以代替乘2操作,但是这个并不代表两者是一样的。

    思考一下:如果任意一个十进制的数左位移32位,右边补位32个0,十进制岂不是都是0了?当然不是!!! 当int 类型的数据进行左移的时候,当左移的位数大于等于32位的时候,位数会先求余数,然后再进行左移,也就是说,如果真的左移32位 12345 << 32 的时候,会先进行位数求余数,即为 12345<<(32%32) 相当于 12345<< 0 ,所以12345<< 33 的值和12345<<1 是一样的,都是 24690。

    右移(>>)

    同样,还是以12345这个数值为例,12345右移1位: 12345>>1。

    img

    右移后得到的值为 6172 和int 类型的数据12345除以2取整所得的值一样,所以有些时候也会被用来替代除2操作。另外,对于超过32位的位移,和左移运算符一样,会先进行位数求余数。

    无符号右移(>>>)

    无符号右移运算符和右移运算符是一样的,不过无符号右移运算符在右移的时候是补0的,而右移运算符是补符号位的。以下是-12345二进制表示:

    img

    对于源码、反码、补码不熟悉的同学,请自行学习,这里就不再进行补充了讲解了,这里提醒一下,在右移运算符中,右移后补0,是由于正数 12345 符号位为0 ,如果为1,则应补1。

    img

    运算符的优先次序

    在对一个表达式进行计算时,如果表达式中含有多种运算符,则要安运算符的优先次序一次从高向低进行。运算符的优先次序如下:

    img

    小白鼠实验应用

    我们到此可以回归题目,用代码把题目做出来:

    代码如下:

     public static void main(String[] args) {
    
    
            //结果反推第几瓶
            System.out.println("结果反推第几瓶:");
            Scanner input = new Scanner(System.in);
            System.out.println("请用户输入总共死亡的个数:");
            int dead = input.nextInt(); // dead用于储存死亡的个数
    
            System.out.println("它们的编号从小到大依次为(从0开始):");
            int[] deadRats = new int[dead]; // number数组用于储存死亡试验品的标签序号
            for(int i = 0; i < dead; i++){
                deadRats[i] = input.nextInt();
            }
    
            double  result = 0;
            for(int i = 0; i < deadRats.length; i++){
                result =  result + Math.pow(2, deadRats[i]);
            }
            System.out.println("毒药编号:" + result);
    
    
            //验算实验过程
    
            //编号每一个小白鼠,1代表正常,0代表死亡
            int[] srcRatsArray = new int[10];
            for(int i = 0; i < 10; i++){
                srcRatsArray[i] = 1;
            }
            //定义1000瓶水和药,不为零的即为毒药,从编号1开始,那么1000瓶,长度为1001
            int[] bottleArray = new int[1001];
    
            //记录结果的老鼠数组
            int[] dtsRatsArray = new int[10];
            for(int i = 0; i < 10; i++){
                dtsRatsArray[i] = 1;
            }
    
            System.out.println("请指定毒药编号:");
            int bottle = input.nextInt();
            bottleArray[bottle] = 1;
    
    
            //利用异或(^)运算, 活着的老鼠 1 碰到毒药 1 就会为0
            for(int j = 1; j < 1001; j++){
                String s = Integer.toBinaryString(j);
                char[] chars = s.toCharArray();
                //遍历j的二进制每一位
                for(int x = chars.length - 1; x >= 0; x--){
                    if(chars[x] == '1'){
                        //index需要倒序
                        int realIndex = 9 - x;
                        //记录死亡的结果
                        if((srcRatsArray[realIndex] ^ bottleArray[j])== 0){
                            dtsRatsArray[realIndex] = 0;
                        }
                    }
                }
            }
    
            for(int i = 0; i < 10; i++){
                if(dtsRatsArray[i] == 0){
                    System.out.println("死亡老鼠:" + i);
                }
    
            }
        }
    

    参考:

    Java基础篇——Java运算符

    Java位运算原理及使用讲解

    Java语言位运算符详解

    相关文章

      网友评论

          本文标题:Java位运算

          本文链接:https://www.haomeiwen.com/subject/sxjzrktx.html