文章目录
- 1. 无算术运算符的加法 Add two numbers without using arithmetic operators
- 2. 无算术运算符的加1操作 Add 1 to a given number without using arithmetic operators
- 3. 检查相反符号 Detect if two integers have opposite signs
- 4. 无比较运算符比较两个整数 Compare two integers without using comparison operators
- 5. 置最右二进制位1为0 Turn off the rightmost set bit
- 6. 数二进制形式中的1 Count set bits in an integer
位操作有太多的神奇写法,这里总结一部分。
1. 无算术运算符的加法 Add two numbers without using arithmetic operators
(1) 题意
如果有一个题目,要求我们实现一个 add
函数,不使用包括 +,++,-,--,..
等算术运算符,完成两个整型数的相加。
两个比特的相加,可以用 ^
来代替(可能存在进位);进位一个比特,可以用 &
来实现。类似的,数字逻辑中,完成两个单独的比特相加的是半加器(half adder
) ,通过半加器的串联,可以实现全加器。
一个朴素的思维是,从最高位开始,x
和 y
的每一位相异或 ^
,如果有进位,则对结果的高位异或 1
,有进位就继续。或者从最低位开始?这样想太麻烦了。
(2) 思路
我们直接对 x
和 y
进行操作,相当于并行进位。
(1) 如果 x
和 y
在每一个二进制位都不同,x^y
就是 x+y
。如:0101 ^ 1010 = 1111
。
(2) 如果存在相同的二进制位(都是 1
),那么需要用 &
来将其吸纳进去。都是 0
的情况无关紧要。
x = 7 = 0000 0111
y = 5 = 0000 0101
x+y = 0000 1100
x=x^y = 0000 0010 //x^y为0的二进制位代表的x,y对应位可能都是0, 或都是1
c=x&y = 0000 0101 //x&y为1的二进制位标记出x,y对应位都是0的情况, 这代表这些位都需要进位
//进位: y = c << 1 = 0000 1010, 即准备对这些位的高一位进行“加法”
//加法的^, 递归进行
x=x^y = 0000 1000
c=x&y = 0000 0010
//进位: y = c << 1 = 0000 0100
//加法的^
x=x^y = 0000 1100 //此时x和y在每一个二进制位都不同
c=x&y = 0000 0000 //c=x&y=0, 不用再进位
(3) 模板代码
下面的写法可以作为模板。
// C++ Program to add two numbers without using arithmetic operator
#include <bits/stdc++.h>
using namespace std;
int add(int x, int y) {
//iterate utill there is no carry
while (y) {
//the carry contains all the common bits of x and y
int carry = x & y;
//now, x is the SUM of bits of x and y
//where at least one of the bits is not set
x = x ^ y;
//carry is right-shifted by 1 so that adding it to x
y = carry << 1;
}
return x;
}
int main() {
cout << add(15, 32);
return 0;
}
递归版: ```cpp int add(int x, int y) { if (y == 0) return x; return add(x ^ y, (x & y) << 1); } ``` ## (4) 习题 [POJ 1001 A + B Problem](https://blog.csdn.net/myRealization/article/details/106750883) .
2. 无算术运算符的加1操作 Add 1 to a given number without using arithmetic operators
(1) 题意
写一个函数 addOne
给一个整数加1,不使用任何算术运算符。
Examples:
Input: 12
Output: 13
Input: 6
Output: 7
(2) 思路1
其一:使用 add
函数,用 ^
和 &
:
x = 12 = 0000 1100
y = 1 = 0000 0001
x^y = 0000 1101 = 13 //结果
x = 7 = 0000 0111
y = 1 = 0000 0001
x=x^y = 0000 0110
c=x&y = 0000 0001 //左移为y = 0000 0010, 进行进位加法
-----
x=x^y = 0000 0100
c=x&y = 0000 0010 //左移为y = 0000 0100, 进行进位加法
-----
x=x^y = 0000 0000
c=x&y = 0000 0100 //左移为y = 0000 1000, 进行进位加法
-----
x=x^y = 0000 1000
c=x&y = 0000 0000 //退出
感觉太麻烦了,有没有更简单的写法呢?其实可以从上面的第二个例子看出,如果我们要对 7
加1,可以将其二进制表示形式中的最右边的那个 0
之后的所有 1
全部翻转为 0
,然后将之前的 rightmost 0
翻转为 1
。
比如说,为了给 x = 1100 0111
加1,我们可以先找到 rightmost 0
,1100 0111;翻转 flip
所有在红字 0
后的比特,可以得到 1100 0000;然后翻转 flip
这个红字 0
,得到 1100 1000
,就是结果。
(3) 模板代码1
#include <bits/stdc++.h>
using namespace std;
//无算术运算符加1
int addOne(int x) {
int m = 1;
//flip all the set bits until we find rightmost 0
while (x & m) {
x = x ^ m;
m <<= 1;
}
//flip the rightmost 0
x = x ^ m;
return x;
}
int main()
{
cout << addOne(13); //0000 1011->0000 1100
return 0;
}
## (4) 思路2 由于补码 `2's Complement` 的广泛使用,我们知道 `[-x]补 = ~[x]补 + 1` 。因此,有 `~([-x]补 - 1) = [x]补` ,即 `~[-(x+1)]补 = [x]补` => `[x+1]补 = -(~[x]补)` 。
所以,为了得到 x+1
,我们写成 -(~x)
即可。
(5) 模板代码2
#include <bits/stdc++.h>
using namespace std;
//无算术运算符加1
int addOne(int x) {
return (-(~x));
}
int main()
{
cout << addOne(13); //0000 1011->0000 1100
return 0;
}
3. 检查相反符号 Detect if two integers have opposite signs
(1) 题意
给出两个有符号整数,写一个函数判断两个整数的符号是否不同,不同则返回 true
。0
视作正数。实质上相当于判断两个整数补码的符号位是否不同。
Examples:
Input: -1 100
Output: true
Input: -1 -200
Output: false
(2) 思路和代码一 使用比较运算符
使用两次比较操作,可以实现题意:
bool oppositeSigns(int x, int y) {
return (x < 0) ? (y >= 0) : (y < 0);
(3) 思路和代码二 位运算
由于使用比较运算符,可能涉及到分支预测和指令预取,一旦预取错误,可能清空多个时间周期的指令。因此会比较慢。为此,我们代之以位运算符。
可以注意到,两个有符号数 x,y
,如果符号相异,则符号位一个为 1
,另一个为 0
,所以 x ^ y
的符号位为 1
,小于零。代码如下:
//C++ Program to Detect if two integers have opposite signs
#include <bits/stdc++.h>
using namespace std;
bool oppositeSigns(int x, int y) {
return ((x ^ y) < 0);
}
int main() {
int x = 123, y = -100;
if (oppositeSigns(x, y)) cout << "Signs are opposite" << endl;
else cout << "Signs are not opposite" << endl;
return 0;
}
一次位运算和一次比较比起两次比较效率更高。针对某些 `int` 是32位的系统,我们可以采用如下的写法,检查 `x ^ y` 的 $31^{th}$ 位是否是 `1` ,从而完全排除比较运算。 ```cpp bool oppositeSigns(int x, int y) { return ((x ^ y) >> 31); } ```
4. 无比较运算符比较两个整数 Compare two integers without using comparison operators
(1) 题意
给出两个整数,检查 A
和 B
是否相等,不使用任何比较运算符。
Examples:
Input: A = 5, B = 6
Output: 0
Input: A = 5, B = 5
Output: 1
Note: 1 = "Yes" and 0 = "No"
(3) 思路和代码 异或
思路很简单,两个整数异或 A ^ B
,结果为零则相等。用减法也可以,不过这是位运算专题,就不这么写了。
//C++ Programs to compare two integers without any comparison operator
#include <bits/stdc++.h>
using namespace std;
//return true if (A ^ B) == 0 else return false
bool equalNumber(int A, int B) {
return !(A ^ B);
}
int main() {
int A = 5, B = 6;
cout << equalNumber(A, B) << endl;
return 0;
}
5. 置最右二进制位1为0 Turn off the rightmost set bit
(1) 题意
写一个程序,将一个整数的二进制形式最右边的 1
置为 0
。
Examples :
Input: 12 (00...01100)
Output: 8 (00...01000)
Input: 7 (00...00111)
Output: 6 (00...00110)
(2) 思路
这很简单,当初写子集生成的迭代版时,就用到了这个 bits magic
。为了给 n
最右边的 1
置零,我们可以用 (n-1) & n
得到这个结果。
(3) 代码
#include <bits/stdc++.h>
using namespace std;
// unsets the rightmost set bit of n and returns the result
int fun(unsigned int n) {
return n & (n - 1);
}
int main() {
int n = 7;
cout << "7 after unsetting the rightmost set bit " << unsetRight1(7) << endl;
cout << "-1 after unsetting the rightmost set bit " << unsetRight1(-1) << endl;
return 0;
}
6. 数二进制形式中的1 Count set bits in an integer
(1) 题意
写一个高效率的程序,数出数的二进制形式中的 1
的数量。
Examples :
Input : n = 6
Output : 2
Binary representation of 6 is 110 and has 2 set bits
Input : n = 13
Output : 3
Binary representation of 13 is 1101 and has 3 set bits
(2) 思路和代码1(简单方法)
搜索所有位,计数即可。
#include <bits/stdc++.h>
using namespace std;
//Count set bits in an integer
unsigned int countSetBits(unsigned int n) {
unsigned int count = 0;
while (n) {
count += n & 1;
n >>= 1;
}
return count;
}
int main() {
int i = 9;
cout << countSetBits(i);
return 0;
}
(3) 思路和代码2 位运算
将5中的做法 n & (n - 1)
一直使用,直到原来的数为 0
,即可知道其二进制形式的中 1
有多少个。
unsigned int countSetBits(unsigned int n) {
unsigned int count = 0;
while (n) {
n &= (n - 1);
++count;
}
return count;
}
(4) 思路和代码3 查表 ★★★
使用 lookup table
搜索表,能够在
O(1)
\text{O(1)}
O(1) 的时间内完成计数。
- 首先,我们假设
int
是32
位的,然后将32
位分成4
个8
位数(0-255
); - 对每个
8
位数,用& 0xff
将其截出,得到一个0-255
的数字; - 然后用
4
个数字,到lookuptable
中查询它们的二进制形式中的1
有多少个;
自然,从上面的步骤可以发现,我们要提前准备好 lookuptable
这张表,然后查表即可:
- 因为不可能创建一个规模为
2^31 - 1
的表,所以我们无法直接用原数值来查表;而是分成了4
个八位数; - 从下面的几个数字,我们可以得到规律:
number binary countSetBits 0 0000 0000 0 = 0 1 0000 0001 1 2 0000 0010 1 3 0000 0011 2 4 0000 0100 1 5 0000 0101 2 6 0000 0110 2 .... 255 1111 1111 8 ① number是0时,countSetBits=0; (基准点) ② number是偶数时, number相当于(number / 2)(可能是奇数或偶数)左移一位, countSetBits不变 ③ number是奇数时, number相当于(number / 2)左移一位, 再加1
- 由此,我们可以递推,写出
lookuptable[256]
的初始化程序。
举一个例子,如果 n = 354
,则二进制形式为 0000000000000000000000101100010
:
分割为8个bits块:
In Binary : 00000000 | 00000000 | 00000001 | 01100010
In decimal : 0 0 1 98
现在对setBits使用lookuptable进行计数:
LookupTable[0] = 0
LookupTable[1] = 1
LookupTable[98] = 3
所以总共的setBits count = 4.
代码如下:
//C++ program to count number of set bits using lookup table in O(1) time
#include <iostream>
using namespace std;
unsigned int lookuptable[256];
//generate lookup table
void initialize() {
lookuptable[0] = 0;
for (int i = 0; i < 256; ++i)
lookuptable[i] = (i & 1) + lookuptable[i >> 1];
}
//count set bits using lookup table and return set bits count
unsigned int countSetBits(int N) { //4
unsigned int count = lookuptable[N & 0xff] +
lookuptable[(N >> 8) & 0xff] +
lookuptable[(N >> 16) & 0xff] +
lookuptable[(N >> 24) & 0xff];
return count;
}
int main() {
initialize();
unsigned int n = 354;
cout << countSetBits(n) << endl;
return 0;
}
(5) 思路和代码4 查表 ★★★
做法是 mapping numbers with the bit
。简单的维持一个数值到 setBits count
的 map/array
。这里 array
长为 16
,保持 4
个比特。
int num_to_bits[16] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
然后我们只需要递归查表即可。
O(1)
\text{O(1)}
O(1) 的复杂度。代码如下,这个函数能够适应 char/short/int/long...
等:
#include <bits/stdc++.h>
using namespace std;
int num_to_bits[16] = {0, 1, 1, 2, 1, 2, 2, 3,
1, 2, 2, 3, 2, 3, 3, 4};
unsigned int countSetBitsRec(unsigned int num) {
int nibble = 0;
if (num == 0) return num_to_bits[0];
nibble = num & 0xf;
//use map to find count in last nibble
//then plus recursively the counts of remaining nibbles
return num_to_bits[nibble] + countSetBitsRec(num >> 4);
}
int main() {
int num = 31;
cout << countSetBitsRec(num);
return 0;
}