注:此文内容来自于对【数据结构与算法之位运算】课程所做的笔记
一、二进制中1的个数
问题:给定一个无符号整型变量,求其二进制表示中“1”的个数。
相似问题:判断整数A转换成整数B需要的次数。(A ^ B
,再求“1”的个数)
分析:很容易想到右移,然后判断奇偶性,复杂度与最左侧的1的位置相关。
int oneCount(unsigned int n){
int c = 0;
while(n){
c += n & 1;
n >>= 1;
}
return c;
}
优化1.:利用判断一个整数是否是2的幂采用的方法,每一次判断都会找到一个1,直到整数变为0为止。复杂度与1的个数相关。
int oneCount1(unsigned int n){
int c = 0;
while(n){
n &= (n - 1); // 清除最低位的1
c++;
}
return c;
}
优化2.:采用并行化规约思想进行优化。在GPU编程中,为了加速求和操作,会采用并行化规约技术使多个线程参与计算,从而将复杂度由O(n)将为O(logn). 位运算可以看成不同 bit 之间的线程并行。
int oneCount2(unsigned int n){
/*获得偶数位的值*/ /*获得奇数位的值*/ // 常数的二进制表示
n = (n & 0x55555555) + ((n >> 1) & 0x55555555); // 0101010101010101...
n = (n & 0x33333333) + ((n >> 2) & 0x33333333); // 0011001100110011...
n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f); // 0000111100001111...
n = (n & 0x00ff00ff) + ((n >> 8) & 0x00ff00ff); // 0000000011111111...
n = (n & 0x0000ffff) + ((n >> 16) & 0x0000ffff); // 0000000000000000...
return n;
}
优化3.:编译器内置位运算,GCC下是__builtin_popcount
,VS下是__popcnt
。
int oneCount3(unsigned int n){
return __builtin_popcount(n); // VS: __popcnt(n)
}
二、最左侧1问题
问题:给定一个整数,判断该整数最左侧1的位置 (most significant bit or count leading zeros problem)
way 1.:通过逐次移位获得最高位(左移右移皆可),复杂度较高。
int left_most_one_1(int n){
int pos = -1;
while(n){
n >>= 1;
pos++;
}
return pos;
}
way 2.:通过二分法寻找最左侧1,复杂度较way1降低很多。
int left_most_one_2(int n){
if(n == 0) return -1;
int exp = 4;
int pos = (1 << exp); // 从16的位置开始二分
while(exp > 0){
exp--;
if(n >> pos) pos += (1 << exp);
else pos -= (1 << exp);
}
ret