位运算及应用《一》
1.位运算功能简介
操作符 | 功能 |
---|---|
<<(shl) | 操作数左移,相当于乘以2 ˆ k |
>>(shr) | 操作数右移,相当于除以2 ˆ k |
>>> | 无符号右移,左边空出的位一概补0 |
&(and) | 某数二进制特定位置0,取某数二进制指定位 |
| | 某数二进制特定位置1 |
ˆ | 某数二进制特定位取反 |
~ | 某数二进制取反 |
注:不能使用带符号右移位来实现带符号整数除以2的幂的运算,如果被除数是负数,而且有非零位被移出,那么需要在移位结果上加一
2.位运算技巧
功能 | 实例 | 位运算 |
---|---|---|
去掉最后一位 | 01011000=>00101100 | x >> 1 |
在最后加一个0 | 01011000=>010110000 | x << 1 |
在最后加一个1 | 01011000=>010110001 | x << 1 + 1 |
析出最右侧的1① | 01011000=>00001000 | x & (-x) |
析出最右侧的0② | 10100111=>00001000 | ~x & (x + 1) |
识别后缀0的掩码③ | 01011000=>00000111 | ~x & (x - 1),或 ~(x | -x),或 (x & -x) - 1 |
识别最右侧1和后缀0的掩码④ | 01011000=>00001111 | x ˆ (x - 1) |
把右侧尾部连续的0变成1 | 01011000=>01011111 | x | (x - 1) |
把右侧尾部连续的1变成0 | 01010111=>01010000 | x & (x + 1) |
把右侧连续的1位串改为0位串⑤ | 01011000=>01000000 | ((x | (x - 1)) + 1) & x |
把最后一位变成1 | 01011000=>01011001 | x | 1 |
把最后一位变成0 | 01011001=>01011000 | (x | 1)-1 |
最后一位取反 | 01011001=>01011000 | x ˆ 1 |
把右数第k位变成1 | 01011001=>01111001(k = 6) | x | (1 << (k - 1)) |
把右数第k位变成0 | 01011001=>010011001(k = 5) | x & ~(1 << (k - 1)) |
右数第k位取反 | 01011001=>01111001(k = 6) | x ˆ (1 << (k - 1)) |
取末k位 | 01101101->00001101(k = 5) | x & (1 << k - 1) |
取右数第k位 | 01101101=>1(k = 4) | (x >> (k - 1)) & 1 |
把末k位变成1 | 00101001->00101111(k = 4) | x | ((1 << k) - 1) |
末k位取反 | 01011001=>01010110(k = 4) | x ˆ ((1 >> k) - 1) |
把右起第一个0变成1 | 01011001=>01011011 | x | (x + 1) |
取右边连续的1 | 10101111=>00001111 | (x ˆ (x + 1)) << 1 |
x循环左移k次 | 10101001=>10011010(k = 4) | (x << k) | (x << (32 - k)) |
x循环右移k次 | 10101001=>00110101(k = 3) | (x >> k) | (x >> (32 - k)) |
32位整数高16位和低16位交换 | 0xFFFFAAAA=>0xAAAAFFFF | (x << 16) |
去掉右起第一个1的左边⑥ | 10101000=>00001000 | x & (x ˆ (x - 1)) |
**注: ①如果没有1位则生成所有位均为0的字
②如果没有0位则生成所有位均为0的字
③如果x = 0则生成所有位都为1的字其中第一个公式具有指令级并行的性质
④如果所有位都为0则生成所有位都为1的字
⑤对于j>k>0,对这一公式的运算结果做0检测可以用来检查一个非负整数是否具有
2^j - 2^k的形式。
⑥最后这一个在树状数组中会用到。
3.二进制补码公式
-x = ~x + 1 = ~(x - 1)
~x = -x - 1
-(~x) = x + 1
~(-x) = x - 1
x + y = x - ~y - 1 = (x | y) + (x & y)
x - y = x + ~y + 1 = (x | ~y)-(~x & y)
x ^ y = (x | y) - (x & y)
x | y = (x & ~y) + y
x & y = (~x | y) - ~x
x == y = ~((x - y) | (y - x))
x != y = (x - y) | (y - x)
x < y = (x - y) ^ ((x ^ y) & ((x - y) ^ x))
x <= y = (x | ~y) & ((x ^ y) | ~(y - x))
x < y = (~x & y) | ((~x | y) & (x - y))//无符号x,y比较
x <= y = (~x | y) & ((x ^ y) | ~(y - x))//无符号x,y比较
4.位运算的实际应用
1.实现swap()函数
void swap(long long& a,long long& b){
a ^= b ^= a ^= b;
}
2.位运算小技巧
①条件语句替换
if(x != a)
x = b;
可替换为 x = a ^ b ^ x;
②判断一个无符号数是不是2的幂
bool IsTwoExp(unsigned int x){
return(((x & (x - 1)) == 0) && (x != 0));
}
③求Gray码的第n个数
unsigned int GetNGray(unsigned int n){
return (n ^ (n >> 1));
}
④判断一个无符号数是不是2的幂减一
bool IsTwoExpDec(unsigned int x){
return ((x & (x + 1)) == 0);
}
⑤判断奇偶数
bool IsOdd(int x){
return ((x & 1) == 1);
}
bool IsEven(int x){
return ((x & 1) == 0);
}
⑥求两个数的平均值
int GetAverage(int x,int y){
return (x & y + ((x ^ y) >> 1));
}
⑦求32整数的绝对值
unsiged int abs(int x){
int y = (x >> 31);
return (x ^ y) - y;
}
unsiged int abs(int x){
int y = (x >> 31);
return (x + y) ^ y;
}
unsiged int abs(int x){
int y = (x >> 31);
return (x ^ (-y)) + y;
}
⑧对2^k取模
unsigned int mod(unsigned int x,int k){
return x & ((1 << k) - 1);
}
3.统计某数二进制中1的数目
代码1
int count_1(int x){
int count = 0;
while(x != 0){
x &= (x - 1);
count++;
}
return count;
}
代码2
int count_1(int x){
x ^= (x >> 1);
x ^= (x >> 2);
x ^= (x >> 4);
x ^= (x >> 8);
x ^= (x >> 16);
return (x & 1);
}
代码3
int count_1(int x){
x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);
x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);
x = (x & 0x0000FFFF) + ((x >> 16)& 0x0000FFFF);
return x;
}
3.统计某数二进制前导0的数目
int pre_0(unsigned x)
{
if (x == 0) return(32);
int n = 1;
if ((x >> 16) == 0) {n = n +16; x = x <<16;}
if ((x >> 24) == 0) {n = n + 8; x = x << 8;}
if ((x >> 28) == 0) {n = n + 4; x = x << 4;}
if ((x >> 30) == 0) {n = n + 2; x = x << 2;}
n = n - (x >> 31);
return n;
}
5.32位数二进制逆序输出
int reverse(int x){
x = ((x & 0x55555555) << 1) | ((x & 0xAAAAAAAA) >> 1);
x = ((x & 0x33333333) << 2) | ((x & 0xCCCCCCCC) >> 2);
x = ((x & 0x0F0F0F0F) << 4) | ((x & 0xF0F0F0F0) >> 4);
x = ((x & 0x00FF00FF) << 8) | ((x & 0xFF00FF00) >> 8);
x = ((x & 0x0000FFFF) << 16)| ((x & 0xFFFF0000) >> 16);
return x;
}
6.0到N这N + 1个数二进制1的个数总和
int count_sum_1(int n,int m)
{
int tmp1 = n >> m;
int tmp2 = 1 << m;
if(tmp1 == 0)
return 0;
if((tmp1 & 1) == 1)
return tmp2 * tmp2 + (n & (tmp2 - 1)) + 1 + count_sum_1(n,m+1);
else
return (n >>(m + 1)) * tmp2 + count_sum_1(n,m+1);
}
n皇后问题位运算版
初始化 upperlim:=(1 shl n)-1
procedure test(row,ld,rd:longint);
var
pos,p:longint;
begin
if row<>upperlim then
begin
pos:=upperlim and not (row or ld or rd);
while pos<>0 do
begin
p:=pos and -pos;
dec(pos,p);
test(row+p,(ld+p)shl 1,(rd+p)shr 1);
end;
end;
else inc(sum);
end.
Gray码问题
var
x,y,m,n,u:longint;
begin
readln(m,n);
for x:=0 to 1 shl m-1 do begin
u:=(x xor (x shr 1)) shl n;//输出数的左边是一个m位的Gray码
for y:=0 to 1 shl n-1 do
write(u or (y xor (y shr 1)),' ');//并上一个n位Gray码
writeln;
end;
end.
注:本文1,3小节摘录自超级大笨狼的博客
本文2,4小节摘录自Matrix67的博客