位操作是各种考试和面试中经常出的题型,在一些算法中使用位运算,不仅能提高效率,而且还能简化代码。
- 约定1:本文中位操作所涉及的整数均指无符号整数。
- 约定2:本文中以ALLBIT表示所有位都为1的整数。
- 约定3:一个整数的最高位在左端,最小位在右端。在计算位的序号时,从右往左数。最低位的序号为1。
位运算符
- &:与
- |:或
- ~:非
- ^:异或
- <<:左移
- >>:右移
集合操作
我们可以用一个整数表示集合,例如一个32位的int型变量,可以表示一个至多有32个成员的集合。某位被设置则表示元素在集合中,否则表示元素不在集合中。
集合的并
A | B
集合的交
A & B
集合的差
A & ~B
集合的补集
~A 或
ALLBIT ^ A
集合中添加第n个元素
A |= 1 << n
集合中删除第n个元素
A &= ~(1 << n)
检查第n个元素是否在集合中
A & (1 << n)
查找最高位和最低位
最简单也非常高效的方法是在循环中依次测试各位,直到找到1个设置的位为止。
这里有一个查找最低位数值的方法,假设A不为0:A & ~(A - 1)
注意,上面查找的是最低位的数值,而不是最低位的序号。
如果你使用的是gcc编译器,使用gcc编译器的内置函数,可以优化查找最低位最高位的序号。
- __builtin_ctz:返回低位连续0的个数
- __builtin_clz:返回高位连续0的个数
注意:不能对0使用这两个gcc内置函数,对于0返回的值是不确定的。
判断一个数是不是2的方幂(判断一个数是不是只有1位被设置)
假定A 不为0,如果A & (A - 1)为0,则说明此数为2的方幂。
计算1的个数
gcc编译器提供了内置函数来完成此任务:
- __builtin_popcount
如果不能使用此函数。我们可以循环测试每一位,这个效率也不会太慢。当让如果已知1的个数比较少,可以依次清除末尾1的方法。代码如下
int count = 0;
while (A)
{
++count;
A &= (A-1);
}
翻转整数中的位
x = ((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1); // 偶数位右移1位,奇数位左移1位
x = ((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2); //
x = ((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4);
x = ((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8);
x = ((x & 0xffff0000) >> 16) | ((x & 0x0000ffff) << 16);
算法说明:
- 第一步,以2位为单位进行分组,交换组内前半部分(只有1位)和后半部分(只有1位)的顺序,则每组内位已经翻转;
- 第二步,以4位为单位进行分组,交换组内前半部分(有2位)和后半部分(有2位)的顺序,则每组内4位已经翻转;
- 同理,进行第三、四、五步,在第五步,以32位为1组,交换后,组内位已经完成翻转。