位运算操作常见技巧

2020/10/14 Skill 共 5575 字,约 16 分钟

位运算操作常见技巧

基本的位操作

  • 与( & )每一位进行比较,两位都为1,结果为1,否则为0(-4 & 1 = 0)
  1 0 0 1 1 -->(19)[10]  表示10进制中的19
& 1 1 0 0 1 -->(25)[10]
------------------------------
  1 0 0 0 1 -->(17)[10]
  • 或 每一位进行比较,两位有一位是1,结果就是1(-4 或 1 = -3)
  1 0 0 1 1 -->(19)[10]
| 1 1 0 0 1 -->(25)[10]
------------------------------
  1 1 0 1 1 -->(27)[10]
  • 非( ~ ) 每一位进行比较,按位取反(符号位也要取反)(~ -4 = 3)
~ 1 0 0 1 1 -->(19)[10]
-----------------------------
  0 1 1 0 0 -->(12)[10]
  • 异或( ^ )每一位进行比较,相同为0,不同为1(^ -4 = -3)
  1 0 0 1 1 -->(19)[10]
^ 1 1 0 0 1 -->(25)[10]
-----------------------------
  0 1 0 1 0 -->(10)[10]
  • 左移( « ) 整体左移,右边空出位补零,左边位舍弃 (-4 « 1 = -8)
int a = 8;
a << 3;
移位前0000 0000 0000 0000 0000 0000 0000 1000 -->(8)[10]
移位后0000 0000 0000 0000 0000 0000 0100 0000 -->(64)[10]  相当于 X 2^3
  • 右移( » ) 整体右移,左边空出位补零或补1(负数补1,整数补0),右边位舍弃 (-4 » 1 = -2)
unsigned int a = 8;
a >> 3;
移位前0000 0000 0000 0000 0000 0000 0000 1000 -->(8)[10]
移位后0000 0000 0000 0000 0000 0000 0000 0001 -->(1)[10] 相当于 / 2^3

int a = -8;
a >> 3;
移位前1111 1111 1111 1111 1111 1111 1111 1000 -->(-8)[10]
移位前1111 1111 1111 1111 1111 1111 1111 1111 -->(-1)[10] 
  • 无符号右移( »> )同»,但不管正数还是负数都左边位都补0 (-4 »> 1 = 2147483646)

初阶位操作

  • 交换两个数
int x = 1 , y = 2; 
x^=y;  // x = 0011
y^=x;  // y = 0001 -->(1)[10]
x^=y;  // x = 0010 -->(2)[10] 
//x =2, y =1  x变成y y变成x
  • 加1操作
int x =1;
x =-(~x); // 0001 --> (~x)后得到1110 --> -(~x) 后得到0010 也就是2,实现了+1操作
//x = 2
  • 减1操作
int x =2;
x =~(-x); // 0010 -->(-x)后得到1110 --> ~(-x) 后得到 0001 也就是1,实现了-1操作
  • 或 操作和空格转英文字符为小写字符
'a'|' '='a'  
// 'a' --> (1100001)[2] 二进制表示 其是(97)[10]
// ' ' --> (0100000)[2] 二进制表示 其是(32)[10]
  1100001 -->(97)[10]
| 0100000 -->(32)[10]
------------------------------
  1100001 -->(97)[10]    还是'a'本身
'A'|' '='a'  
// 'A' --> (1000001)[2] 二进制表示 其是(65)[10]
// ' ' --> (0100000)[2] 二进制表示 其是(32)[10]
  1000001 -->(65)[10]
| 0100000 -->(32)[10]
------------------------------
  1100001 -->(97)[10]    'A'-->'a'

常用的ASCII值

'0' : 48
'A' : 65
'a' : 97
  • 与(&)操作和下划线转英文字符为大写字符
'a'&'_'='A'  
// 'a' --> (1100001)[2] 二进制表示 其是(97)[10]
// '_' --> (1011111)[2] 二进制表示 其是(95)[10]
  1100001 -->(97)[10]
| 1011111 -->(95)[10]
------------------------------
  1000001 -->(65)[10]    'a'-->'A'
'A'&'_'='A'  
// 'A' --> (1000001)[2] 二进制表示 其是(65)[10]
// '_' --> (1011111)[2] 二进制表示 其是(95)[10]
  1000001 -->(65)[10]
| 1011111 -->(95)[10]
------------------------------
  1000001 -->(65)[10]    'A'-->'A'
  • 异或(^)操作与空格实现大小写英文字符互转
'a'^' '='A'  
// 'a' --> (1100001)[2] 二进制表示 其是(97)[10]
// ' ' --> (0100000)[2] 二进制表示 其是(32)[10]
  1100001 -->(97)[10]
^ 0100000 -->(32)[10]
------------------------------
  1000001 -->(65)[10]   'a'-->'A'
'A'^' '='a'  
// 'A' --> (1000001)[2] 二进制表示 其是(65)[10]
// ' ' --> (0100000)[2] 二进制表示 其是(32)[10]
  1000001 -->(65)[10]
^ 0100000 -->(32)[10]
------------------------------
  1100001 -->(97)[10]    'A'-->'a'
  • 两个数是否异号
int x =-1 , y =2;
x ^ y <0 // true 
  1111 -->(-1)[10]
^ 0010 -->( 2)[10]
------------------------------
  1101 -->(-3)[10]  //前面的1111省略掉了
    
int x =1 , y =2;
x ^ y <0 // false 
  0001 -->(1)[10]
^ 0010 -->(2)[10]
------------------------------
  0011 -->(3)[10]  
  • 判断奇数偶数:只要根据数的最后一位是 0 还是 1 来决定即可,为 0 就是偶数,为 1 就是奇数
0 == (a & 1) // true的时候是偶数 false时奇数
  • 交换符号:将正数变成负数,负数变成正数

整数取反加1,正好变成其对应的负数(补码表示);负数取反加一,则变为其原码,即正数

int reversal(int a) {
  return ~a + 1;
}
  • 位操作统计二进制中 1 的个数
统计二进制1的个数可以分别获取每个二进制位数然后再统计其1的个数此方法效率比较低这里介绍另外一种高效的方法同样以 34520 为例我们计算其 a &= (a-1)的结果

- 第一次计算前1000 0110 1101 1000 计算后1000 0110 1101 0000
- 第二次计算前1000 0110 1101 0000 计算后1000 0110 1100 0000
- 第三次计算前1000 0110 1100 0000 计算后1000 0110 1000 0000 我们发现每计算一次二进制中就少了一个 1则我们可以通过下面方法去统计
    
count = 0  
while(a){  
  a = a & (a - 1);  
  count++;  
}      

进阶位操作

lowbit:x&(-x)

详解链接,一文掌握数据数组

枚举所有子集

    private void enumerateSubset() {
        int s, n = 5;
		//(1<<n)等价于1*2^n 
        for (s = 0; s < (1 << n); s++) {
            System.out.printf("%s--->", addZeroForNum(Integer.toBinaryString(s), 5));
            for (int i = 0; i < n; i++) {
                //1<<i 相当于拿2的整数倍数 1 2 4 8 16 这样
                if ((s & (1 << i)) != 0) {//不为0时表示true
                    System.out.printf("%d ", i);
                }
            }
            System.out.printf("\n");
        }
    }
//比如s = 7  ->二进制数为:00111 
//当i = 0,1,2时才输出s=7中的数 
//s&(1<<0) == 7&1 == 00111&001 == 1  输出 0
//s&(1<<1) == 7&2 == 00111&010 == 1  输出 1
//s&(1<<2) == 7&4 == 00111&100 == 1  输出 2 分别对应0、1、2下标索引

所有子集:

不选时:\(C_{5}^0\) 1种

选1个元素时:\(C_{5}^1\) 对应:0/1/2/3/4 一共5种

选2个元素时:\(C_{5}^2\) 一共10种

选3个元素时:\(C_{5}^3\) 一共10种

选4个元素时:\(C_{5}^4\) 一共5种

选5个元素时:\(C_{5}^5\) 一共1种

一共32种选择

00000--->
00001--->0 
00010--->1 
00011--->0 1 
00100--->2 
00101--->0 2 
00110--->1 2 
00111--->0 1 2 
01000--->3 
01001--->0 3 
01010--->1 3 
01011--->0 1 3 
01100--->2 3 
01101--->0 2 3 
01110--->1 2 3 
01111--->0 1 2 3 
10000--->4 
10001--->0 4 
10010--->1 4 
10011--->0 1 4 
10100--->2 4 
10101--->0 2 4 
10110--->1 2 4 
10111--->0 1 2 4 
11000--->3 4 
11001--->0 3 4 
11010--->1 3 4 
11011--->0 1 3 4 
11100--->2 3 4 
11101--->0 2 3 4 
11110--->1 2 3 4 
11111--->0 1 2 3 4 

image-20201012091442282

运算符优先级

img

扩展

转换

    /**
     * 把2进制字符串key,转成10进制keys
     * 数字2代表进制,可以是各种进制,转换成10进制
     *
     * @param bin
     * @return
     */
    public static String bin2Ten(String bin) {
        String res = String.valueOf(Integer.parseInt(bin, 2));
        System.out.printf("binary:%s--->ten:%s\n", bin, res);
        return res;
    }
	

    private void testOne() {
        for (int i = 1; i <= 10; i++) {
            int positive = i;
            int negative = -i;
            String nStr = Integer.toBinaryString(negative);
            System.out.println(String.format("%d:%s\t%d:%s\t%s",
                    positive, addZeroForNum(Integer.toBinaryString(positive), 4)
                    , negative, nStr.substring(nStr.length() - 4),
                    addZeroForNum(Integer.toBinaryString(positive & negative), 4)
                    )
            );
        }
    }

	
    public static String addZeroForNum(String str, int strLength) {
        int strLen = str.length();
        if (strLen < strLength) {
            while (strLen < strLength) {
                StringBuffer sb = new StringBuffer();
                sb.append("0").append(str);// 左补0
                // sb.append(str).append("0");//右补0
                str = sb.toString();
                strLen = str.length();
            }
        }
        return str;
    }

链接

Reference

  • https://www.cnblogs.com/td15980891505/p/5447424.html
  • https://www.zhihu.com/question/38206659/answer/294900478
  • https://www.cnblogs.com/hellocheng/p/7402170.html
  • https://www.luogu.com.cn/blog/sxyzsycpqst521/wei-yun-suan-qiang-tai-dei-ling-ren-hai-pa
  • http://www.matrix67.com/blog/archives/264(位运算简介及实用技巧)

文档信息

Search

    Table of Contents