定义

  • 定义:其实说白了,位运算就是直接对整数在内存中的二进制位进行操作
  • 特点:位运算直接对内存数据进行操作,不需要转成十进制,速度很快

基本位运算符

  1. and( & ) : 通常用于二进制位操作,如一个数&1的结果就是取二进制的最末尾,可以用来判断整数奇偶
  2. or( | ) : 通常用于二进制定位上的无条件赋值,例如一个数or1的结果就是把二进制最末位强行变成1,若要变成0则减1就可以了,实际意义就是把这个数强行变成最接近的偶数
  3. xor( ^ ) : 通常用于对二进制的特定一位进行取反,可以对两个数进行交换
  4. not( ~ ) : 取反,注意的是整数类型有没有符号,如果是无符号整数,那么得到的值为其与上界的差
  5. left shift( « ) : 左移,在二进制数每添一位0就相当于乘2,因此每左移一位就相当于乘2;并且在常量的定义上,比如可以通过1 << 16 来表示65535

对应与集合运算则是交集、并集、差集和补集,假设集合 A 是 {0, 3, 5, 6},集合 B 是 {0, 2, 4, 6},全集为 {0, 1, 2, 3, 4, 5, 6, 7}那么:

  • & 交集 Intersection {0, 6}
  • | 并集 Union {0, 2, 3, 4, 5, 6}
  • ^ 差集 Symmetric difference {2, 3, 4, 5}
  • ~ 补集 Complement {1, 3, 5, 7}

位 运算符简单应用

经典题型:做数独时我们需要27个Hash表来统计每一行、每一列和每一个小九宫格里已经有哪些数了。此时,我们可以用27个小于2^9的整数进行记录。例如,一个只填了2和5的小九宫格就用数字18表示(二进制为000010010),而某一行的状态为511则表示这一行已经填满。需要改变状态时我们不需要把这个数转成二进制修改后再转回去,而是直接进行位操作。在搜索时,把状态表示成整数可以更好地进行判重等操作。

功能位运算
去掉最后一位x » 1
最后加一个0x « 1
最后加一个1(x « 1) + 1
把最后一位变成1x | 1
把最后一位变成0(x | 1) - 1
最后一位取反x ^ 1
把右数第k位变成1x | (1 « (k-1))
把右数第k位变成0x & ~(1 « (k-1))
右数第k位取反x ^ (1 « (k-1))
取末三位x & 7
取末k位x & ( ( 1 « k ) -1)
取右数第k位x » (k -1) & 1
把末k位变成1x | ( ( 1 « k) - 1 )
末k位取反x ^ ( ( 1 « k) -1 )
把右边连续的1变成0x & (x + 1)
把右起第一个0变成1x | (x+1)
把右边连续的0变成1x | (x-1)
取右边连续的1(x ^ (x+1)) » 1
去掉右边第一个1的左边(树状数组)x & (x ^ (x-1))

如何在数组中对指定位置置1

就像上面表格提到的,把右数第k位变成1,这里也是这个原理,但是要注意数组中,一般整型32位,所以

//在第i个位置写i
for (i = 0; i < 40; i ++) {
b[i / 32] |= ( 1 << (i%32) ) ```> 判断奇偶

只需要根据最末尾是0还是1即可判定,为0就是偶数,为1就是奇数

​```c
i & 1

交换两数

a ^= b 即 a = (a ^ b);b ^=a即b = (b^a) = b^(a^b),由于^满足交换律,b = b^b^a,因为一个数和自己异或的结果为0,并且0与任何数异或都不变,所以b = a;a ^= b 相当于a = a^b=(a^b)^a = b

if (a != b){
a ^= b;
b ^= a;
a ^= b;
}

变换符号,把正数变为负数,负数变为正数

变换符号只需要取反加1即可

~a + 1

求绝对值

方法1:对于负数可以通过对其取反加1来得到整数,先移位来取符号为,int i = a » 31;如果a为正数,i等于0,如果为负数,i等于-1,

int i = a >> 31;
return i == 0 ? a : (~a + 1);

方法2:任何数,与0异或都保持不变,与-1(0xFFFFFFFFF)异或相当于取反,因此a与i异或再减i也可以得到绝对值

int i = a >> 31;
return ((a ^ i) - i);