【位运算】位运算的妙用技巧

位运算的妙用技巧

      • 1.按位与 &
            • ①判断奇偶
            • ②统计二进制中 1 的个数
            • ③取余
            • ④判断一个整数是不是2的幂,对于一个数 x >= 0
            • ⑤取模运算
            • ⑥整数的平均值
            • ⑦位清0和位置1
      • 3.按位异或 ^
            • ①交换两数
            • ②判断赋值
      • 4.左移>>
            • ①乘法运算
            • ②整数的平均值
            • ③进行高低位交换
            • ④二进制逆序:使用高低位交换
      • 5.右移<<
            • ①除法运算
            • ②取int型变量a的第k位
            • ③进行高低位交换
      • 6.取反~
            • ①求相反数(交换符号)
            • ②求绝对值

1.按位与 &

①判断奇偶
a&1   = 0 偶数
a&1   = 1 奇数

if((a & 1)==0) {
 //偶数
}
②统计二进制中 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++;  
}  
③取余

让a对k进行取余,那么就可以让 a & (k-1)

3%2=1
||
3&(1)=1
  0011
 &0001
----------
  0001
④判断一个整数是不是2的幂,对于一个数 x >= 0
boolean power2(int x)
{
    return ((x&(x-1))==0)&&(x!=0);
}
⑤取模运算
a % (2^n) 等价于 a & (2^n - 1)
⑥整数的平均值

对于两个整数x,y,如果用 (x+y)/2 求平均值,会产生溢出,因为 x+y 可能会大于INT_MAX,但是我们知道它们的平均值是肯定不会溢出的。

int average(int x, int y)   //返回X,Y 的平均值
{   
     return (x&y)+((x^y)>>1);
}
⑦位清0和位置1

(1)清0公式:

内容 公式
1.将某个数据A的第n位清0,其它位保持不变: A &= ~(0x1 << n);
2.将某个数据A从第n位开始,连续两个bit位清0,其它位保持不变: A &= ~(0x3 << n);
3.将某个数据A从第n位开始,连续三个bit位清0,其它位保持不变: A &= ~(0x7 << n);
4.将某个数据A从第n位开始,连续四个bit位清0,其它位保持不变: A &= ~(0xF << n);
5.将某个数据A从第n位开始,连续五个bit位清0,其它位保持不变: A &= ~(0x1F << n);
6.将某个数据A从第n位开始,连续六个bit位清0,其它位保持不变: A &= ~(0x3F << n);
7.将某个数据A从第n位开始,连续七个bit位清0,其它位保持不变: A &= ~(0x7F << n);
8.将某个数据A从第n位开始,连续八个bit位清0,其它位保持不变: A &= ~(0xFF << n);

(2)置1公式

内容 公式
将某个数据A的第n位置1,其它位保持不变: A
2.将某个数据A从第n位开始,连续两个bit位置1,其它位保持不变: A
3.将某个数据A从第n位开始,连续三个bit位置1,其它位保持不变: A
4.将某个数据A从第n位开始,连续四个bit位置1,其它位保持不变: A
5.将某个数据A从第n位开始,连续五个bit位置1,其它位保持不变: A
6.将某个数据A从第n位开始,连续六个bit位置1,其它位保持不变: A
7.将某个数据A从第n位开始,连续七个bit位置1,其它位保持不变: A
8.将某个数据A从第n位开始,连续八个bit位置1,其它位保持不变: A

3.按位异或 ^

性质:
a^a=0
a^0=a
a^b^a=b

①交换两数
//普通操作
void swap(int &a, int &b) {
  a = a + b;
  b = a - b;
  a = a - b;
}

//位与操作
void swap(int &a, int &b) {
  a ^= b;
  b ^= a;
  a ^= b;
}
②判断赋值
if (x == a) 
	 x= b;
else x= a;      等价于 x= a ^ b ^ x;

4.左移>>

①乘法运算
a * (2^n) 等价于 a<< n
②整数的平均值

对于两个整数x,y,如果用 (x+y)/2 求平均值,会产生溢出,因为 x+y 可能会大于INT_MAX,但是我们知道它们的平均值是肯定不会溢出的。

int average(int x, int y)   //返回X,Y 的平均值
{   
     return (x&y)+((x^y)>>1);
}
③进行高低位交换

将无符号数 a>>8 即可得到其高 8 位移到低 8 位,高位补 0;将 a<<8 即可将 低 8 位移到高 8 位,低 8 位补 0,然后将 a>>8 和 a<<8 进行或操作既可求得交换后的结果。

a = (a >> 8) | (a << 8);
④二进制逆序:使用高低位交换

在字符串逆序过程中,可以从字符串的首尾开始,依次交换两端的数据。在二进制中使用位的高低位交换会更方便进行处理,这里我们分组进行多步处理。
第一步:以每 2 位为一组,组内进行高低位交换

交换前: 10 00 01 10 11 01 10 00
交换后: 01 00 10 01 11 10 01 00

第二步:在上面的基础上,以每 4 位为 1 组,组内高低位进行交换

交换前: 0100 1001 1110 0100
交换后: 0001 0110 1011 0001

第三步:以每 8 位为一组,组内高低位进行交换

交换前: 00010110 10110001
交换后: 01100001 00011011

第四步:以每16位为一组,组内高低位进行交换

交换前: 0110000100011011
交换后: 0001101101100001

对于上面的第一步,依次以 2 位作为一组,再进行组内高低位交换,这样处理起来比较繁琐,下面介绍另外一种方法进行处理。

先分别取原数 10000110 11011000 的奇数位和偶数位,将空余位用 0 填充:

原数:  10000110 11011000
奇数位: 10000010 10001000
偶数位: 00000100 01010000

再将奇数位右移一位,偶数位左移一位,此时将两个数据相或即可以达到奇偶位上数据交换的效果:

原数:  10000110 11011000
奇数位右移一位: 0 10000010 1000100
偶数位左移一位:0000100 01010000 0
两数相或得到: 01001001 11100100

上面的方法用位操作可以表示为:

取a的奇数位并用 0 进行填充可以表示为:a & 0xAAAA
取a的偶数为并用 0 进行填充可以表示为:a & 0x5555 
因此,上面的第一步可以表示为:a = ((a & 0xAAAA) >> 1) | ((a & 0x5555) << 1)
同理,可以得到其第二、三和四步为:
a = ((a & 0xCCCC) >> 2) | ((a & 0x3333) << 2)
a = ((a & 0xF0F0) >> 4) | ((a & 0x0F0F) << 4)
a = ((a & 0xFF00) >> 8) | ((a & 0x00FF) << 8)

因此可总结为:

unsigned short a = 34520;

a = ((a & 0xAAAA) >> 1) | ((a & 0x5555) << 1);
a = ((a & 0xCCCC) >> 2) | ((a & 0x3333) << 2);
a = ((a & 0xF0F0) >> 4) | ((a & 0x0F0F) << 4);
a = ((a & 0xFF00) >> 8) | ((a & 0x00FF) << 8);

5.右移<<

①除法运算
a / (2^n) 等价于 a>> n
      例: 12/8 == 12>>3
②取int型变量a的第k位
a>>k&1
③进行高低位交换

将无符号数 a>>8 即可得到其高 8 位移到低 8 位,高位补 0;将 a<<8 即可将 低 8 位移到高 8 位,低 8 位补 0,然后将 a>>8 和 a<<8 进行或操作既可求得交换后的结果。

a = (a >> 8) | (a << 8);

6.取反~

①求相反数(交换符号)

交换符号将正数变成负数,负数变成正数

int reversal(int a) {
  return ~a + 1;
}
②求绝对值

整数的绝对值是其本身,负数的绝对值正好可以对其进行取反加一求得。

即我们首先判断其符号位(整数右移 31 位得到 0,负数右移 31 位得到 -1,即 0xffffffff),然后根据符号进行相应的操作。

int abs(int a) {
  int y = a >> 31;
  return y== 0 ? a : (~a + 1);
}

你可能感兴趣的:(LeetCode算法刷题,操作系统,编程笔记(问题解决),算法,位运算,二进制)