这是C++算法基础-基础算法专栏的第十六篇文章,专栏详情请见此处。
ps:转眼间暑假已过半,我在这段时间也积累了很多文章,所以到开学(9月1日)为止,每个周我将会在周三和周六发文章(o゚▽゚)o
引入
在计算机中,数据都是用二进制储存的,所以对它们的运算,也就是位运算,它的速度是相当快的。
下面我们就来讲位运算的实现。
定义
位运算是基于整数的二进制表示进行的运算。
过程
位运算一般有三种作用:
-
高效地进行某些运算,代替其它低效的方式。
-
表示集合(常用于状压DP)。
-
题目本来就要求进行位运算。
而我们目前最常见的就是第1种和第3种作用。
下面我们通过基本位运算与位运算的应用来讲解。
基本位运算
基本的位运算共6种,分别为按位与、按位或、按位异或、按位取反、左移和右移(为了方便叙述,下文中省略「按位」)。
与、或和异或
与、或和异或都是将两个整数作为二进制数,对二进制表示中的每一位逐一运算。
运算 | 运算符 | 解释 |
与 | & | 只有两个对应位都为1时才为1 |
或 | | | 只要两个对应位中有一个1时就为1 |
异或 | ^ | 只有两个对应位不同时才为1 |
例如:我们令A=60,B=15,则
A&B:
0011 1100(60)
& 0000 1111(15)
----------------
0000 1100(12)
A|B:
0011 1100(60)
| 0000 1111(15)
----------------
0011 1111(63)
A^B:
0011 1100(60)
^ 0000 1111(15)
----------------
0011 0011(51)
取反
然后是取反,用符号~表示,作用是将该数字每一位取反(0变成1,1变成0),有符号整数的符号位同样也会被取反。
例如:我们令A=60,则
~A:
~ 0011 1100(60)
-----------------
1100 0011(-61)
取反有一个性质,对于数字,那么
则为
。
左移和右移
最后是左移和右移,用符号<<和>>表示,作用是将该数字的二进制表示中,向左或向右移动指定位数。
左移时低位需用0填充,高位越界后则丢弃;而右移高位需用符号位填充,低位越界后则丢弃。
因此,,
。
例如:我们令A=60,则
A<<1:
<< 0011 1100(60)
------------------
0111 1000(120)
A>>1:
>> 0011 1100(60)
-----------------
0001 1110(30)
复合赋值运算符
同+=,-=等运算符类似,位运算也有复合赋值运算符:&=,|=,^=,<<=,>>=(取反是单目运算,所以没有),这些运算很简单,这里就不再讲解。
位运算的应用
基本的位运算比较简单,但我们要重点学习的是位运算的应用。
成对变换
对于非负整数(
是异或运算):
- 当
为偶数时,
- 当
为奇数时,
因此,0与1、2与3、4与5...关于操作构成成对变换。这一性质可以在存储与获取无向边时使用。
lowbit运算
lowbit运算是指一个数二进制表示下最低位的1连同后面的0,例如可表示为
,则
。
lowbit运算的公式为:
lowbit(n)=n&(-n)
二进制状态压缩(操作一个数的二进制位)
二进制状态压缩是指操作一个数的二进制位,具体有以下五种常见操作:
1. 取出整数在二进制表示下的第
位:
(n>>k)&1
2. 取出整数在二进制表示下的第
位(后
位):
n&((1<<k)-1)
3. 把整数在二进制表示下的第
位取反:
n^(1<<k)
4. 对整数在二进制表示下的第
位赋值为
:
n|(1<<k)
5. 对整数在二进制表示下的第
位赋值为
:
n&(~(1<<k))
汉明权重
汉明权重是一串符号中不同于零符号(zero-symbol)的个数。对于一个二进制数,它的汉明权重就等于它位置上为1的个数。
计算二进制数的汉明权重,可以不断地去掉这个数在二进制下的最后一位(即右移1位),维护一个答案变量,在除的过程中根据最低位是否为1更新答案。
但是使用lowbit操作是更好的:我们将这个数不断地减去它的lowbit并计数,直到这个数变为0。
汉明权重的计算代码如下:
int popcount(int x){
int cnt=0;
while(x){
cnt++;
x-=x&-x;
}
return cnt;
}
上一篇-双指针算法的实现(三题详解) C++算法基础专栏文章 下一篇-离散化的实现
每周六更新一篇文章,内容一般是自己总结的经验或是在其他网站上整理的优质内容
点个赞,关注一下呗~