bit操作是非常trick的东西,通过bit操作,可以高效率的完成许多事情,简单的如判断一个数的奇偶,找一个数最低位的1,返回一个数的二进制表示中1的个数
复杂点的,有位图操作,这个在海量数据处理中经常会用到,还有linux2.6的O(1)的调度算法,也是使用的一个140位的位图,(5个unsigned long)
基本的比特操作:&,|,^,~(按位取反),<<,>>,>>>(循环右移)
复合操作符,如&=、|=、 ^=、<<=、>>=
微软实习生面试时,有一题就是给一堆64位的无符号long表示的长整型数,让你通过一堆比特操作,给出一个最小值。
下面开始简单的题目(以下主要是参考的nash_和MoreWindows的blog)
nash_ 点击打开链接
MoreWindows
位操作符的运算优先级比较低,要使用括号
1 int的最大值和最小值(这里是补码表示法)
(1<<31) - 1 = 2147483647 有优先级,所以必须加括号~(1<<31)= 2147483647
1 << 31 = -2147483648
同理,求long的最大值:((long)1 << 127) -1
2 与2相关的计算,*2,/2,* 2的m次,/ 2的m次
n << 2n >> 2
n << m
n >> m
3 判断奇偶性
n % 2 == 0 / 1
x & 1 == 0 / 1因为奇数的最低位为1,偶数的最低位为0
4 交换两个数a、b
a = a^b
b = b^a
a = a^b
^运算满足交换律,一个数和自己异或的结果为0
任何数与0异或都会不变的
5 取绝对值
n > 0 ? n : - n
(n ^ (n >> 31)) - (n >> 31)
n右移31位,求的是n的符号。如果是正的,n >> 31得0,负数得-1
如果n是正数,n ^ (n>>31) - (n>>31) = n ^ 0 - 0 = n
如果n是负数,n ^ (n >> 31) - (n>>31) = n ^ (-1) - (-1)
一个负数与-1的异或,结果等于其绝对值-1。
-2 ^ -1 = 1
-3 ^ -1 = 2
-1 ^ -1 = 0
那么将结果-(-1),相当于+1,就是其绝对值了
或者
(n >> 31) ? n : (~a + 1)
任何数,与0异或都会保持不变,与-1即0xFFFFFFFF异或就相当于取反。
6 求两个数的最大值、最小值
a > b ? a : b
b & ((a-b) >> 31) | a & ( ~(a-b) >> 31)
其中,(a-b)右移31位,与上一题一样,正数返回0,负数返回-1
也就是说,如果a >= b,(a-b) >> 31 = 0 ; 否则等于-1
如果a >= b,~ (a-b) >> 31 = -1
如果a >= b,原式 = b & 0 | a & -1 = a
反之一样
-- 最小
a & ((a-b) >> 31 ) | b & (~(a-b) >> 31)
7 判断两个数符号是否相同
a ^ b >= 0 同号
8 判断一个数是否2的幂次
n > 0 ? (n & (n-1) == 0 ) : false
9 求两个数的均值
(x + y ) >> 2
((x ^ y) >> 1 ) + (x & y)
其中,x^y右移1位,得到的是x、y其中一个为1的位,然后除以2
x & y 得到的是x,y都为1的部分。
加起来就是平均数了
10 从低位到高位,取n的第m位
( n >> (m-1) ) & 1
先右移m-1次,然后跟1做&操作
11 从低位到高位,将m的第n位置1
n | (1 << (m-1))
将1左移 m-1 次,得到 1 00***0,
12 从低位到高位,将m的第n位置0
n & ~( 1 << (m-1)) 取反操作
13 取相反数:
~n +1 或者 (n ^ -1 ) + 1
14 高低位交换
二进制数的前8位为“高位”,后8位为“低位”
10000110 11011000 ---> 11011000 10000110
只要将x>>8与x<<8这两个数相或就可以了
x >> 8 | x << 8
15 二进制逆序
字符串的逆序/类似于归并排序的分组处理
10000110 11011000 --->
00011011 01100001
第一步:每2位为一组,组内高低位交换
10 00 01 10 11 01 10 00
-->01 00 10 01 11 10 01 00
第二步:每4位为一组,组内高低位交换
0100 1001 1110 0100
-->0001 0110 1011 0001
第三步:每8位为一组,组内高低位交换
00010110 10110001
-->01100001 00011011
第四步:每16位为一组,组内高低位交换
01100001 00011011
-->00011011 01100001
原 数 10000110 1100
奇数位 1_0_0_1_ 1_0_1_0_
偶数位 _0_0_1_0 _1_1_0_0
将下划线用0填充,可得
原 数 00
奇数位 偶数位 000100 00
再将奇数位右移一位,偶数位左移一位,此时将这两个数据相或即可以达到奇偶位上数据交换的效果了。
原 数 奇数位右移 01000001 01000100
偶数位左移 00 00
相或得到 01001001 11100100
16 二进制中1的个数
每次右移,& 0x01,看结果是否为1。直到将原数移位成0为止
x & (x -1 ) 会将最低位的1置0
另一种高效的做法
第一步:每2位为一组,组内高低位相加
10 00 01 10 11 01 10 00
-->01 00 01 01 10 01 01 00
高低位就是一半、一半(这里就是1位+1位)
x = ((x & 0xAAAA) >> 1) + (x & 0x5555);
第二步:每4位为一组,组内高低位相加
0100 0101 1001 0100
-->0001 0010 0011 0001
这里是两位+两位
a = ((a & 0xCCCC) >> 2) + (a & 0x3333);
第三步:每8位为一组,组内高低位相加
00010010 00110001
-->00000011 00000100
a = ((a & 0xF0F0) >> 4) + (a & 0x0F0F);
第四步:每16位为一组,组内高低位相加
00000011 00000100
-->00000000 00000111
在Java中不能直接使用二进制表示数字,可以使用8进制或者16进制来间接表示
Integer.toHexString(x)可以输出二进制字符串(不过高位的0没有补足)
17 缺失的数字
很多成对出现数字保存在磁盘文件中,注意成对的数字不一定是相邻的,如2,3,4,2……,由于意外有一个数字消失了,如何尽快的找到是哪个数字消失了?
将这组数都进行异或。由于X^X = 0
最后剩余的那个数,一定是唯一的数
变体:如果有2个数是唯一出现的,其余都是两两出现的,怎么找出这三个数
不妨设all为所有数异或的结果,all肯定有一位是1,不妨设第i位为1
根据i是否为1,将原先的数分成两堆。分别,进行异或,得到的结果,就是那两个唯一出现的数
如果有三个数是唯一出现的呢?跟两个数唯一出现类似?
问题是,如果根据第i位是否为1 分堆,有可能,选出的i,不能区分这三个数(不能将这三个数分到不同的堆)
1 ^ 1 ^ 1 = 1
1 ^ 1 ^ 0 = 0
1 ^ 0 ^ 0 = 1
这个我再想想