运算升级:位运算
今天在刷递归题的时候看到看到两道比较有意思的题
鉴于我搜索回溯都还没学,数据处理也比较菜,就想到尝试用位运算的方法来解决。
不说废话了来认识一下位运算的一些性质吧。
为什么使用位运算
数字在计算机中以二进制形式储存,所以使用位运算符计算比使
用算数运算符速度更快
对于一类特定的问题,如子集问题,改写数字等,使用二进制&
位运算是十分直观的方法。
位运算的基础运算符
先来看一段示例程序
1 |
|
运行得到
1 | 17 119 102 -86 340 10 |
分析
首先将A B改写为二进制数
6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|
A | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
B | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
&:按位与运算符
与 逻辑运算符&&类似,当参与运算的值均为1时才返回1
A | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
---|---|---|---|---|---|---|---|
B | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
A&B | 0 | 0 | 1 | 0 | 0 | 0 | 1 |
|: 按位或运算符
与逻辑运算符 || 相似,当参与运算的值至少有一个为1就返回1
A | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
---|---|---|---|---|---|---|---|
B | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
AorB | 1 | 1 | 1 | 0 | 1 | 1 | 1 |
^ : 按位异或运算符
只有参与运算的值不完全相同才返回1,若均相同(不管同为1还
是0)则输出0.
A | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
---|---|---|---|---|---|---|---|
B | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
A^B | 1 | 1 | 0 | 0 | 1 | 1 | 0 |
~ : 非运算符
输出将输入数据每一位取反
A | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
---|---|---|---|---|---|---|---|
~A | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
<< : 左移运算符
将输入的数据向左(向高位)移位,后面用0补齐
8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|---|
A | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
A<<2 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
将两数转换为十进制
A = 85
A<<2 = 340
可以发现将A左移2位相当于乘以4(2的二次方)
所以得出:A<<n=A*2^n
: 右移运算符
将输入的数据向右(向低位)移位,前面用0补齐
6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|
A | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
A>>2 | 0 | 0 | 1 | 0 | 1 | 0 | 1 |
A = 85
A>>2 = 21
可以发现,与左移相反,右移n位可以将数除以2的n次方
喜闻乐见的例题讲解
这道题其实就是让我们从n个元素中选k个相加,判断和是否为质数
让我们先来看一个简单的例子
假设我们有一个5个元素的数组(下标从1开始)
i | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
a[ i ] | 1 | 2 | 3 | 4 | 5 |
对于全集 U={ 1,2 ,3 ,4 ,5 } ,每个子集都包含其中任意个元素。
有没有什么办法可以给这些不同的子集编号呢?
我们可以以元素的存在情况作为依据:
令每个子集的编号为一个5位二进制数,如果其中一位对应的数在子
集中存在,那么那一位为1,否则为0。
以其中一个子集 S = { 1 , 3 , 5 } 为例,得到以下表格
a[i] | 5 | 4 | 3 | 2 | 1 |
---|---|---|---|---|---|
U | 1 | 1 | 1 | 1 | 1 |
S | 1 | 0 | 1 | 0 | 1 |
- 注意:对于二进制数来说,数位由高到低递减。
则U的编号为11111,S的编号为10101,
转换成十进制后,U=31,S=21
我们惊喜的发现,U的大小就等于2^n-1(其中n为元素个数)
通过这种方法,每一个子集都有一个唯一的二进制编号与其一一对应。
接下来该做什么呢?
对于这道题来说,要求其中k个数的和,
就是求含k个元素的子集每个元素的和。
而我们编号之后会发现,如果一个子集有k个元素,
那么它的编号中也必有k个‘1’
(如S = { 1 , 3 , 5 }的编号10101中有3个1)
到此为止,这道题的思路就很清晰了,
先计算U的大小,从1到U枚举S,判断S中’1‘的个数
如果等于k,就把S中的元素相加,判断是否为素数,记录答案即可。
AC程序如下:
1 |
|
这道题其实就是让我们把一个十进制数改写成二的整数次幂之和
所以自然想到采用二进制的做法
我们以题目中的例子进行说明,先将137转换成二进制数观察
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|
137 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
很明显我们可以发现:
需要改写的那一位已经以‘1’的形式存在了
只需要记录数中每一个‘1’在第几位,再对位数递归分解
直到所有的‘1’都在第0~2位上结束。
像这样:
1 | //todo 位运算部分 |
继续来看,我们注意到137的二进制第7位为1,只需要再将7改写为二进制数如下表:
2 | 1 | 0 | |
---|---|---|---|
7 | 1 | 1 | 1 |
此时所有的‘1’都已经在0~2位上,达到了递归的边界,判断输出即可。
附上AC代码:
1 |
|