基础篇中对各种位运算操作进行了详细的介绍说明,在具备基础篇的知识基础上,学习了解一些位运算的奇淫技巧,能够更好的掌握位运算知识,同时在一些算法题中,也可以有更开阔的视野和题解。
斯坦福计算机资料整理:http://graphics.stanford.edu/~seander/bithacks.html#OperationCounting
CHAR_BIT is the number of bits per byte (normally 8).
从十进制转化为二进制的转化方式可以直观的看到,当一个整数除2,最终的余数是0,这个整数是-偶数。相反,余数为1,这个数是-奇数。
偶数: a & 1 == 0
奇数 a & 1 == 1
将数 a 左移1位,等价于 a乘以2; 右移1位,等价于 a除以2.
a >>= 1 ---- a = a / 2;
a <<= 1 ---- a = a * 2;
异或,使用异或实现两个整数的交换。
void swap(int& a, int& b) {
a ^= b; // a = a ^ b;
b ^= a; // b = b ^ (a ^ b) = a
a ^= b; // a = (a ^ b) ^ a = b
}
// 或者使用加减法实现
void swap(int& a, int& b) {
a = a + b;
b = a - b;
a = a - b;
}
正数取反加一,变负数;负数取反加一,变正数。
int reverely(int a) {
return (~a + 1);
}
1111 1111 1111 1011 -5
----------------------------------
~ 0000 0000 0000 0100 ~(-5)
----------------------------------
+1 0000 0000 0000 0101 5
----------------------------------
// 正数取反加1,变成其对应的负数(补码表示)
// 负数取反加1,变成其对应的正数(原码)
正整数的绝对值是其本身,负数的绝对值正好可以对其取反加1获取,通过判断符号位来确定数值的正负值,返回其绝对值。(个人感觉只是一种思路,直接判断与0的大小进行返回更直观些)
// 根据正负值进行
unsigned int abs(int a) {
return (a >= 0) ? a : -a;
}
// 根据符号位值计算
unsigned int abs(int a) {
int r = a >> sizeof(int) * CHAR_BIT - 1;
return r == 0 ? a : ~a + 1;
}
// 对于任何数于0异或,保持不变,与-1异或,等价于对此数取反,上述函数可以转化为
unsigned int abs(int a) {
int mark = a >> sizeof(int) * CHAR_BIT - 1;
return ((a ^ mark) -mark)
}
给定一个整数,求交换高低位之后的数
例如 十进制 10 0000 1010
交换高低位位置 1010 0000
int x = (a >> 4) | (a << 4);
// 交换字节的高低位并不是一个很常见的问题,一些算法题中也会有表现。
字符串或数组的逆序,可以依次交换字符串或数组的首位数据,实现逆序。二进制中实现逆序,使用高低位交换更方便处理
举例说明:
将无符号数的二进制表示进行逆序,求取逆序后的结果。
数字: 35874 1000 1100 0010 0010
逆序: 17457 0100 0100 0011 0001
① 以每2位为一组,组内进行高低位交换
交换前: 10 00 11 00 00 10 00 10
交换后: 01 00 11 00 00 01 00 01
② 以①的结果,进行每4位一组,组内以2位高低位交换
交换前: 0100 1100 0001 0001
交换后: 0001 0011 0100 0100
③ 同理,八位一组,组内以四位进行高低位交换
交换前: 00010011 01000100
交换后: 00110001 01000100
④ 同理,十六位一组..
交换前: 0011000101000100
交换后: 0100010000110001
2位一组,进行交换,实现方式如下:
// 取数值的奇数位,其余位用0填充: a & 0xAAAA
// 取数值的偶数位,其余位用0填充: a & 0x5555
// 实现高低位交换:
a = ((a & 0xAAAA) >> 1) | ((a & 0x5555) << 1)
// 同理 ②③④步为:
a = ((a & 0xCCCC) >> 2) | ((a & 0x3333) << 2)
a = ((a & 0xF0F0) >> 4) | ((a & 0x0F0F) << 4)
a = ((a & 0xFF00) >> 8) | ((a & 0x00FF) << 8)
对一个字节数据,逐个交换其高低位,例如:1101 1010, 经过交换,变成 0101 1011.
a = (a << 4) | (a >> 4); 1010 1101
a = ((a << 2) & 0xcc) | ((a >> 2) & 0x33); 1010 0111
a = ((a << 1) & 0xaa) | ((a >> 1) & 0x55); 0101 1011
可以抽象的理解:1234 5678
a = 5678 1234
a = 7856 3412
a = 8765 4321
以此实现字节数据的诸位交换。
统计数值二进制位1的个数,可以分别获取二进制位中的数,然后统计1出现的次数。
// 直观的可以使用循环来计算
unsigned int bitCounter(unsigned int a) {
unsigned int c = 0;
while(a) {
c += a & 1;
a >>=1;
}
return c;
}
// 相对高效的计算
unsigned int bitCounter(unsigned int a) {
unsigned int c = 0;
while(a) {
a &= (a - 1)
++c;
}
return c;
}
// 比如:2048 二进制 1000 0000 0000 , 2048 -1 0111 1111 1111,
// 第二种算法中只需要一次的循环就可以求出其中的位。
sign = v >> sizeof(int) * CHAR_BIT - 1; // if v < 0 then -1, else 0
sign = +1 | (v >> (sizeof(int) * CHAR_BIT - 1)); // if v < 0 then -1, else +1
sign = 1 ^ ((unsigned int)v >> (sizeof(int) * CHAR_BIT - 1)); // if v < 0 then 0, else 1
// 位运算方法一:
int min(int x, int y) {
return y ^ ((x ^ y) & -(x < y));
}
// if x < y = true, y ^ ((x ^ y) & -1) = x
// if x < y = false, y ^ ((x ^ y) & 0) = y
int max(int x, int y) {
return x ^ ((x ^ y) & -(x < y));
}
// if x < y = true, x ^ ((x ^ y) & -1) = y
// if x < y = false, x ^ ((x ^ y) & 0) = x
leetcode 题库试题
比较笨的方法,观察2的幂次值: 0001, 0010, 0100, 1000 …
bool isPowerOfTwo(int val) {
if (val <= 0) return false;
return (val & (val - 1) == 0);
}
// return val && !(val & (val - 1))
时间有限,工作之余整理,文章开头的斯坦福大学资料总结比较全面,有兴趣的可以直接阅读。根据个人理解所写,如果存在理解偏差或者错误请指出,非常感谢,也希望对你的学习有所帮助。