C++中 的位操作及其部分经典运用

1.位操作的介绍

位操作有:

与:&

或:|

取反: ~

取反符号与非!的区别:

  1. !是逻辑运算符(与||,&&是一类符号),表示逻辑取反,可以把非0值变成0,把0值变为1
  2. ~是位运算符(与|,&是一类符号),表示按位取反,在数值的二进制表示上,将0变为1,将1变为0

异或:^

位移操作:(左移:<<    ,    右移:>>)

 位操作只能用于整形数据,对float和double类型进行位操作会被编译器报错

需要注意的是由于编译器的不同,位移的操作可能有不同的结果:例如对负数的右移动,有些编译器高位补0有些是补上符号位相同的数

 

 

2.位操作的一些应用

 

2.1判断奇偶

只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数。

这也是对二进制末位的判断

 

2.2交换两个数

可以这样理解:

第一步  a^=b 即a=(a^b);

第二步  b^=a 即b=b^(a^b),由于^运算满足交换律,b^(a^b)=b^b^a。由于一个数和自己异或的结果为0并且任何数与0异或都会不变的,所以此时b被赋上了a的值。

第三步 a^=b 就是a=a^b,由于前面二步可知a=(a^b),b=a,所以a=a^b即a=(a^b)^a。故a会被赋上b的值。
再来个实例说明下以加深印象。int a = 13, b = 6;

a的二进制为 13=8+4+1=1101(二进制)

b的二进制为 6=4+2=110(二进制)

第一步 a^=b  a = 1101 ^ 110 = 1011;

第二步 b^=a  b = 110 ^ 1011 = 1101;即b=13

第三步 a^=b  a = 1011 ^ 1101 = 110;即a=6

 

2.3变符号

变换符号就是正数变成负数,负数变成正数。

如对于-11和11,可以通过下面的变换方法将-11变成11

      1111 0101(二进制) –取反-> 0000 1010(二进制) –加1-> 0000 1011(二进制)

同样可以这样的将11变成-11

      0000 1011(二进制) –取反-> 0000 0100(二进制) –加1-> 1111 0101(二进制)

因此变换符号只需要取反后加1即可(因为计算机中存储的是补码)

 

2.4求绝对值

因此先移位来取符号位,int i = a >> 31;要注意如果a为正数,i等于0,为负数,i等于-1。然后对i进行判断——如果i等于0,直接返回。否之,返回~a+1。

对于任何数,与0异或都会保持不变与-1即0xFFFFFFFF异或就相当于取反。因此,a与i异或后再减i(因为i为0或-1,所以减i即是要么加0要么加1)也可以得到绝对值。

 

3.位操作的一些具体题目

3.1用来做空间压缩,例如力扣的1371 https://leetcode-cn.com/problems/find-the-longest-substring-containing-vowels-in-even-counts

用了5位二进制数字记录了32种状态

 

3.2对整数的高位和低位进行交换

思路:高位右移加上低位左移即可

给出一个16位的无符号整数。称这个二进制数的前8位为“高位”,后8位为“低位”。现在写一程序将它的高低位交换。例如,数34520用二进制表示为:

      10000110 11011000

将它的高低位进行交换,我们得到了一个新的二进制数:

      11011000 10000110

它即是十进制的55430。

这个问题用位操作解决起来非常方便,设x=34520=10000110 11011000(二进制) 由于x为无符号数,右移时会执行逻辑右移即高位补0,因此x右移8位将得到00000000 10000110。而x左移8位将得到11011000 00000000。可以发现只要将x>>8与x<<8这两个数相或就可以得到11011000 10000110。用代码实现非常简洁:

 

3.3二进制的逆序

我们这里主要分析这个巧妙的方法,核心思想是:分治法。即:

  • 逆序32位分解为两个逆序16位的
  • 逆序16位分解为两个逆序8位的
  • 逆序8位分解为两个逆序4位的
  • 逆序4位分解为两个逆序2位的

最后一个2位的逆序,直接交换即可。也就是分治递归的终止条件。但是,在上面的过程中,还没有应用到位操作的技巧。根据动态规划的思想,我们可以自底向上的解决这个问题:

  • 每2位为一组,进行交换,完成2位逆序
  • 每4位为一组,前面2位与后面2位交换,完成4位逆序
  • 每8位为一组,前面4位和后面4为交换,完成8位的逆序
  • 每16位为一组,前面8位和后面8位交换,完成16位的逆序

2组16位的交换,完成32位的逆序

通过下面的例子,详解上面的过程,我们以16位为例:10000110 11011000

1000011011011000
0100100111100100
0001011010110001
0110000100011011
0001101101100001

经过4步,逆序完成。推而广之,总的时间复杂度为O(logn),n是二进制的位数。这个方法可以推广到任意位。

示例代码如下:

int v =111;
v =((v >>1)&0x55555555)|((v &0x55555555)<<1);
v =((v >>2)&0x33333333)|((v &0x33333333)<<2);
v =((v >>4)&0x0F0F0F0F)|((v &0x0F0F0F0F)<<4);
v =((v >>8)&0x00FF00FF)|((v &0x00FF00FF)<<8);
v =( v >>16)|( v <<16);System.out.println(v);

上面的思路理解了,代码不难理解。例如第二行,前边是取偶数位,后面是取奇数位,奇数位左移一位,偶数位右移一位,再取或,就是交换了奇数偶数位。也就是第一个步骤。

 

3.4二进制中1的个数

方法1:利用2.1每次右移一位并判断是否为1,时间复杂度为O(n);

方法2:与运算,我们知道将一个整数减去1之后,其对应的二进制中最右边的一个1会变为0,若其后存在0,则其之后的所有0都会变为1。基于此,设一个整数为n,则 n & (n-1)之后,会消掉n对应的二进制的最右边的1。因此,将一个数中所有1消掉所用的次数,即为该整数对应的二进制中1的个数

 

3.5判断数组中判断一个数是不是2的x次方

n & (n-1)还可以用来判断一个数是不是2的x次方,因为如果是的话,这个数的二进制表示里只会有一个1

 

3.6判断数组中某个数字出现次数的问题

很多成对出现数字保存在磁盘文件中,注意成对的数字不一定是相邻的,如2, 3, 4, 3, 4, 2……,由于意外有一个数字消失了,如何尽快的找到是哪个数字消失了?

由于有一个数字消失了,那必定有一个数只出现一次而且其它数字都出现了偶数次。用搜索来做就没必要了,利用异或运算的两个特性——1.自己与自己异或结果为0,2.异或满足交换律。因此我们将这些数字全异或一遍,结果就一定是那个仅出现一个的那个数。 

 

 

以上应用相关内容,摘录自博客https://blog.csdn.net/weixin_43417960/article/details/83239246

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页