zoukankan      html  css  js  c++  java
  • 位运算

    位运算

    最高位的bit为1的数都是负数,最高位bit为0的数都是正数。
    

    一:位运算符

    1:有符号位移

    含义 运算符 示例
    左移 << 0011 => 0110
    右移 >> 0110 => 0011

    2:无符号位移(>>>逻辑位移)

    正数的原码补码,负数的补码(符号位不变)原码取反+1

    算数位移
    # <<表示左移,不分正负数,低位补0; 
    
    注:以下数据类型默认为byte-8位
    
    左移时不管正负,低位补0
    正数:r = 20 << 2
      20的二进制补码:0001 0100
      向左移动两位后:0101 0000
             结果:r = 80
    
    负数:r = -20 << 2
      -20 的二进制原码 :1001 0100    符号位 : 0是正数,1是负数
      -20 的二进制反码 :1110 1011     反码:符号位不变,其它位取反
      -20 的二进制补码 :1110 1100     补码:反码+1
    
      左移两位后的补码:1011 0000       方法1:  补码 -1 再取反,可得原码    方法2:求补码的补码(就是它的原码)
            反码:1010 1111        
            原码:1101 0000
            结果:r = -80
    
     ---------------------------------------------------------------------------------------------
    >>表示右移,如果该数为正,则高位补0,若为负数,则高位补1;
    注:以下数据类型默认为byte-8位
    
    正数:r = 20 >> 2
      20的二进制补码:0001 0100
      向右移动两位后:0000 0101         
           结果:r = 5
    
    负数:r = -20 >> 2
      -20 的二进制原码 :1001 0100    负数的原码:对应正数的原码符号位取反
      -20 的二进制反码 :1110 1011    符号位不变,其余取反
      -20 的二进制补码 :1110 1100    +1
      右移两位后的补码:1111 1011    
            反码:1111 1010
            原码:1000 0101
            结果:r = -5
       ----------------------------------------------------------------------------------------------
    
          逻辑位移
          >>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0
    
    正数: r = 20 >>> 2
        的结果与 r = 20 >> 2 相同;
        
    负数: r = -20 >>> 2
    注:以下数据类型默认为int 32位
      -20:源码:10000000  00000000   00000000   00010100
        反码:11111111  11111111   11111111   11101011
        补码:11111111  11111111   11111111   11101100
        右移:00111111  11111111   11111111   11111011
        结果:r = 1073741819
          
          java没有<<<
    

    3:基本位运算

    含义 运算符 示例
    按位或 0011
    1011
    ------->
    1011 有1为1
    按位与 & 0011
    1011
    ------->
    0011 全1才为1
    按位取反 ~ 0011 => 1100
    按位异或(相同为0不同为1) ^ 0011
    1011
    -------->
    1000 相同为0不同为1

    4: XOR -异或 运算

    异或:相同为 0,不同为 1。也可用“不进位加法”来理解。

    异或操作的一些特点:

    假设x = 9 1001

    操作 示例
    x ^ 0 = x 异或0就是x本身 1001
    ^ 0000
    ------->
    1001 异或:相同为0,不同为1
    x ^ 1s = ~x ==>1s = ~0 1001
    ^ 1111 1s就是全1
    -------->
    0110 === ~x
    x ^ (~x) = 1s 1001
    ^ 0110
    --------->
    1111 异或:相同为0,不同为1
    x ^ x = 0 1001
    ^1001
    -------->
    0000 异或:相同为0,不同为1
    c = a ^ b => a ^ c = b, b ^ c = a a = 9.b=7
    a: 1001
    b: ^ 0111
    c: 1110
    -----------------------------------
    a: 1001
    c: ^1110
    0111 ===>b=7
    a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c // 结合律

    5:指定位置的位运算

    一条32位的指令,它的最高位是31位,最低位是0位.   以下默认以0开始计。
    
    操作 示例
    1. 将x 最右边的 n 位清零:x& (~0 << n) 假设x:1111 1111
    n=4, ~0 =1,左移动4位==》1111 0000
    做&运算
    1111 1111
    1111 0000
    1111 0000 ===》将右边的4位都清零
    2. 获取x 的第 n 位值(0 或者1): (x >> n) & 1 10的二进制为1010,获取第1位是1,第2位是0(从低位到高位,从第0位开始数)
    1010 --->n =2,右移动2位 ==》10, &1
    10
    01
    00 ===>第二位是0,右数第3位
    3. 获取x 的第 n 位的幂值:x& (1 <<n) x 10: 1010 获取第3位,n==3, 1<<3 ==> 1000
    1010&1000 ===>1000
    4. 仅将第 n 位置为1:x|(1 << n) 或运算,有1结果就为1.
    5. 仅将第 n 位置为0:x & (~ (1 << n)) 与运算: 有0结果就为0
    6. 将x 最高位至第 n 位(含)清零:x& ((1 << n) -1) x:1010 第3位:n==3: 1<<3 ==> 1000 -1 ==> 0111 再&1010
    0010 最高位第三位就清零了
    7. 将第 n 位至第0 位(含)清零:x& (~ ((1 << (n + 1)) -1))

    6:实战位运算要点

    判断奇偶:

    • x % 2 == 1 —> (x & 1) == 1

    • x % 2 == 0 —> (x & 1) == 0

    • x >> 1 —> x / 2.
      即: x = x / 2; —> x = x >> 1;
      mid = (left + right) / 2; —> mid = (left + right) >> 1;

    • X = X & (X-1) 清零最低位的 1

      x:    1010
      x-1: &1001
      --------------
            1000  将最低位的1清0.
      
    • X & -X => 得到最低位的 1,其它位置就清零了

      最高位的bit为1的数都是负数,最高位bit为0的数都是正数。
      
      x=10:   0000 1010  取反+1  就是负数的补码
      -x=-10: 1111 0101 + 1==>   1111 0110
       
          &运算:  0000 1010
               &  1111 0110
                 0000  0010
          
      
              int x = 10;
              System.out.println("x:"+Integer.toBinaryString(x));
              System.out.println("-x:"+Integer.toBinaryString(-x));
              System.out.println(Integer.toBinaryString(x&-x));
      console:
      x:1010
      -x:11111111111111111111111111110110
      10
      
    • X & ~X => 0

      x: 1010
      ~x:0101
       & 0000
      

    二:位运算的应用--leetcode

    191. 位1的个数
    编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
    public class Solution {
       public int hammingWeight(int n) {
            int count = 0; //统计
            while(n !=0){
                count++;
                n &= (n-1); //清零最低位的1,清理一次,就统计一次
            }
    
            return count;
        }
    }
    
    231. 2的幂
    给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
    
    这题的关键是,理解2的N次幂,只有1个1.
    方法1:
    class Solution {
        public boolean isPowerOfTwo(int n) {
            if (n == 0) return false;
        while (n % 2 == 0) n /= 2;
        return n == 1;
        }
    }
    
    如何获取二进制中最右边的 1:x & (-x)。
    如何将二进制中最右边的 1 设置为 0:x & (x - 1)。  
    
    方法2:
    我们通过 x & (-x) 保留了最右边的 1,并将其他位设置为 0 若 x 为 2 的幂,则它的二进制表示中只包含一个 1,则有 x & (-x) = x。
    若 x 不是 2 的幂,则在二进制表示中存在其他 1,因此 x & (-x) != x。
    
    因此判断是否为 2 的幂的关键是:判断 x & (-x) == x。
    class Solution {
      public boolean isPowerOfTwo(int n) {
        if (n == 0) return false;
        long x = (long) n;
        return (x & (-x)) == x;
      }
    }
    方法3:
    2 的幂二进制表示只含有一个 1。 比如:1 就是1  2:10  4:100    8:1000  都是只有1个1,才是2的N次幂
    x & (x - 1) 操作会将 2 的幂设置为 0,因此判断是否为 2 的幂是:判断 x & (x - 1) == 0
    
    class Solution {
      public boolean isPowerOfTwo(int n) {
        if (n == 0) return false;
        long x = (long) n;
        return (x & (x - 1)) == 0;
      }
    }
    
    
    338. 比特位计数
    给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
    输入: 5
    输出: [0,1,1,2,1,2]
    
    方法1:
    时间复杂度:O(nk)O(nk)。对于每个整数 xx,我们需要 O(k)O(k) 次操作,其中 kk 是 xx 的位数。
    空间复杂度:O(n)O(n)。 我们需要 O(n)O(n) 的空间来存储计数结果。如果排除这一点,就只需要常数空间
    
    class Solution {
        public int[] countBits(int num) {
            int[] ans = new int[num+1];
            for (int i = 0; i <= num; i++) {
                 int res = calcOne(i);
                 ans[i] = res;
            }
            return ans;
    
        }
    
        //计算1的个数
        private int calcOne(int i) {
            int count = 0; //统计
            while (i != 0){
                i = i&(i-1);  //清零最低位的1,清理一次,就统计一次
                count++;
            }
            return count;
        }
    }
    
    
    方法2:DP解法详情去看leetcode
    
    190. 颠倒二进制位
    颠倒给定的 32 位无符号整数的二进制位。
    
    public class Solution {
        // you need treat n as an unsigned value
        public int reverseBits(int n) {
            int ans = 0;
            for (int i = 0; i < 32; i++) {
                ans = (ans <<1) + (n&1);  //(n&1) 得到最左边的数值,ans再左移一位,将最低位添加
                n >>=1; //清除最左边的一位
            }
            return ans;
        }
    }
    
  • 相关阅读:
    嵌入式成长轨迹27 【Linux应用编程强化】【中嵌第二阶段】【进程管理】
    嵌入式成长轨迹24【Linux应用编程强化】【Linux下的C编程 下】【实例:Linux命令实现】
    纯CSS代码实现翻页
    Adodb.Stream读取和写入UTF8编码的文件
    对c#拆装箱的性能分析(泛型)
    js自动更换图片代码(收藏)
    提高网站可用性的10个小技巧
    分享下我的家乡语言——湘潭话
    解析用户研究
    HTML5 搭建移动Web应用
  • 原文地址:https://www.cnblogs.com/zhoujun007/p/13897965.html
Copyright © 2011-2022 走看看