位运算

&:按位与

二进制位均为1,则为1。

举例:

1001 & 1000 = 1000

|:按位或

二进制位有一个为1,则为1。

举例:

1001 & 1000 = 1001

^:按位异或

二进制位不同为1。

举例:

1001 ^ 1000 = 0001

~:取反

按位取反,为一元运算符。

举例:

~1001 = 0110

<<:左移运算符

一个数的二进制位全部左移N位,右位补0。

举例:

1001 << 1 = 10010

(a<<b,实际上为a * 2^b)

>>:右移运算符

一个数的二进制位全部右移N位,左位补0还是1取决于计算机系统。

对于无符号数,左位补0。

对于有符号数,有机器将对左位用符号位填补(即“算术移位”),有机器则对左位用0填补(即“逻辑移位”)。即:如果原来符号位为0(正数),则左位补0。如果符号位原来为1(负数),则左位补0还是1,要取决于所用的计算机系统。

当a是正整数时,a>>b == a / (2^b)

当a是负整数时,a>>b == a / (2^b) 上取整。

 

位运算技巧

技巧一:N&(N-1)可以消去N的最后一位的1

应用:

用O(1)时间检测整数n是否是2的幂次(N的二进制表示中只有一个1,且N>0)

二进制表示中有多少个1(循环消1即可)

将整数A转换为B,需要改变多少个bit位(A和B有多少个BIT位不相同,A异或B之后这个数中1的个数)

 

技巧二:子集枚举

对于大小为N的数组,判断N个二进制位中每一位是0还是1,1表示包括该位置数,0表示不包括,可生成2^N个子集

 

技巧三:a^b^b=a

应用:

只有一个数出现一次,剩下都出现两次,找出出现一次的(所有的数异或可以得到唯一的数)

只有一个数出现一次,剩下都出现三次,找出出现一次的(如果只出现一次的数在该二进制位为1,那么这个二进制位在全部数字中出现次数无法被3整除)

只有两个数出现一次,剩下都出现两次,找出出现一次的(所有的元素异或的结果就是res=x^y。并且res!=0,那么我们可以找出res二进制表示中的某一位是1,那么这一位1对于这两个数x,y只有一个数的该位置是1。对于原来的数组,我们可以根据这个位置是不是1就可以将数组分成两个部分。求出x,y其中一个,就能求出两个了。)

1 #include<stdio.h>
 2 
 3 void findNum(int *a,int n)
 4 {
 5     int ans=0;
 6     int pos=0;
 7     int x=0,y=0;
 8     for(int i=0;i<n;i++)
 9         ans^=a[i];
10     int tmp=ans;
11     while((tmp&1)==0){
12     //终止条件是二进制tmp最低位是1
13             pos++;
14             tmp>>=1;
15     }
16     for(int i=0;i<n;i++){
17         if((a[i]>>pos)&1){//取出第pos位的值
18             x^=a[i];
19         }
20     }
21     y=x^ans;
22     if(x>y) swap(x,y);//从大到小输出x,y
23     printf("%d %d
",x,y);
24 }
25 int main()
26 {
27     int a[8]={1,2,2,3,4,4,5,3};
28     findNum(a,8);
29 }

技巧总结

功能 示例 位运算公式
去掉最后一位 101101  ->  10110 x >> 1
最后加一个0 101101  ->  1011010 x << 1
最后加一个1 101101  ->  1011011 (x << 1) + 1
最后一位变成0 101101  ->  101100 (x | 1) - 1
最后一位变成1 101100  ->  101101 x | 1
最后一位取反 101101  ->  101100 x ^ 1
右边第k位变成0 101101  ->  101001     k=3 x | (1 << (k-1))
右边第k位变成1 101001  ->  101101     k=3 x &~ (1 << (k-1))
右边第k位取反 101101  ->  101001     k=3 x ^ (1 << (k-1))
取最后三位 101101  ->  101 x & 0b111
取最后k位 101101  ->  1101        k=4 x & ((1<<k) - 1)
取右边第k位 101101  ->  1             k=4 x>>(k - 1) & 1
右边k位变成1 101101  ->  101111    k=4 x | ((1<<k) - 1)
右边k位取反 101101  ->  100010 x ^ ((1<<k) - 1)
右边连续的1变成0 100101111  ->  100100000 x & (x + 1)
右边连续的0变成1 101101000  ->  101101111 x | (x - 1)
右边第一个0变成1 101101  ->  101111 x | (x + 1)
取右边连续的1 101111  ->  1111 (x ^ (x + 1)) >> 1
去掉右边第一个1的左部分 101101000  ->  1000 x & (-x)

负数的二进制表示

计算机中:负数以原码的补码形式表达

为何101101000 -> 1000   =====    x & (-x) ?

 

296:00000000 00000000 00000001 00101000

296的原码:00000000 00000000 00000001 00101000(正数,按照绝对值大小转换成的二进制数)

-296的原码:10000000 00000000 00000001 00101000(负数按照绝对值大小转换成的二进制数,然后最高位补1)

 

反码:

正数的反码与原码相同。296的反码:00000000 00000000 00000001 00101000

负数的反码为对原码除符号位外取反。-296的反码:11111111 11111111 11111110 11010111

 

补码:

正数的补码与原码相同。296的补码:00000000 00000000 00000001 00101000

负数的补码为对原码除符号位外各位取反,然后在最后一位加1。-296的补码:11111111 11111111 11111110 11011000

 

那么-296在计算机中表示为:11111111 11111111 11111110 11011000

 

因此:296 & (-296) = 8      100101000 & 011011000   =(0b1000)

 

 

 

原文地址:https://www.cnblogs.com/jpzhu/p/14649372.html