算法
常用的一个等式:
-n = ~(n - 1) = ~n + 1 ## 获得int型最大值int getMaxInt(){ return (1 << 31) - 1;//2147483647,由于优先级关系,括号不可省略 return ~(1 << 31); //2147483647 return (1 << -1) - 1;//2147483647 return ((unsigned int) - 1) >> 1;//2147483647 }
获得int型最小值
int getMinInt(){ return 1 << 31;//-2147483648 return 1 << -1;//-2147483648 }
获得long类型的最大值
long getMaxLong(){ return ((unsigned long) - 1) >> 1;//2147483647 c语言版 return ((long)1 << 127) - 1;//9223372036854775807 java版 }
获得long最小值,和其他类型的最大值,最小值同理. ## 2运算
n << 1 // 乘以2 n >> 1 // 除以2 n << m // 乘以2的m次方 n >> m // 除以2的m次方
判断一个数的奇偶性
boolean isOddNumber(int n){ return (n & 1) == 1; }
不用临时变量交换两个数(面试常考)
void swap(int *a,int *b){ (*a) ^= (*b) ^= (*a) ^= (*b); }
通用版(一些语言中得分开写)
a ^= b; b ^= a; a ^= b;
取绝对值
(某些机器上,效率比n>0 ? n:-n 高)
int abs(int n){ return (n ^ (n >> 31)) - (n >> 31); /* n>>31 取得n的符号,若n为正数,n>>31等于0,若n为负数,n>>31等于-1 若n为正数 n^0=0,数不变,若n为负数有n^-1 需要计算n和-1的补码,然后进行异或运算, 结果n变号并且为n的绝对值减1,再减去-1就是绝对值 */ }
取两个数的最大值
(某些机器上,效率比a>b ? a:b高)
int max(int a,int b){ return b & ((a-b) >> 31) | a & (~(a-b) >> 31); /*如果a>=b,(a-b)>>31为0,否则为-1*/ }
C语言版
int max(int x,int y){ return x ^ ((x ^ y) & -(x < y)); /*如果x<y x<y返回1,否则返回0, 与0做与运算结果为0,与-1做与运算结果不变*/ }
取两个数的最小值
(某些机器上,效率比a>b ? b:a高)
int min(int a,int b){ return a & ((a-b) >> 31) | b & (~(a-b) >> 31); /*如果a>=b,(a-b)>>31为0,否则为-1*/ }
C语言版
int min(int x,int y){ return y ^ ((x ^ y) & -(x < y)); /*如果x<y x<y返回1,否则返回0, 与0做与运算结果为0,与-1做与运算结果不变*/ }
判断符号是否相同
boolean isSameSign(int x, int y){ //有0的情况例外 return (x ^ y) >= 0; // true 表示 x和y有相同的符号, false表示x,y有相反的符号。 }
计算2的n次方
int getFactorialofTwo(int n){//n > 0 return 2 << (n-1);//2的n次方 }
判断一个数是不是2的幂
boolean isFactorialofTwo(int n){ return n > 0 ? (n & (n - 1)) == 0 : false; /*如果是2的幂,n一定是100... n-1就是1111.... 所以做与运算结果为0*/ }
对2的n次方取余
int quyu(int m,int n){//n为2的次方 return m & (n - 1); /*如果是2的幂,n一定是100... n-1就是1111.... 所以做与运算结果保留m在n范围的非0的位*/ }
求两个整数的平均值
int getAverage(int x, int y){ return (x + y) >> 1; } int getAverage(int x, int y){ return ((x ^ y) >> 1) + (x & y); /*(x^y) >> 1得到x,y其中一个为1的位并除以2, x&y得到x,y都为1的部分,加一起就是平均数了*/ }
下面是三个最基本对二进制位的操作 ## 从低位到高位,取n的第m位
int getBit(int n, int m){ return (n >> (m-1)) & 1; }
从低位到高位.将n的第m位置设为1
int setBitToOne(int n, int m){ return n | (1 << (m-1)); /*将1左移m-1位找到第m位,得到000...1...000 n在和这个数做或运算*/ }
从低位到高位,将n的第m位置设为0
int setBitToZero(int n, int m){ return n & ~(1 << (m-1)); /* 将1左移m-1位找到第m位,取反后变成111...0...1111 n再和这个数做与运算*/ }
另附一些对程序效率上没有实质提高的位运算技巧,一些也是位运算的常识(面试也许会遇到)
n+1 = -~n n-1 = ~-n -n = ~n+1 -n = (n^-1)+1 x = a ^ b ^ x <=> if(x == a) x = b; if(x == b) x = a; sign(x) = !!n - (((unsigned)n >> 31) << 1)
获取整数二进制表示中最右侧的1
n & (-n) <=> n & ~(n - 1)
二进制中1的个数
用到了n & (n - 1) 由x & (x - 1)消去x最后一位的1可知。不断使用 x & (x - 1) 消去x最后一位的1,计算总共消去了多少次即可。
int countOnes(int num) { int count = 0; while(num != 0) { num = num & (num-1); count++; } return count; }
翻转
// 翻转 unsigned int Bit_Reverse(unsigned int v) { v = ((v >> 1) & 0x55555555) | ((v << 1) & 0xaaaaaaaa); v = ((v >> 2) & 0x33333333) | ((v << 2) & 0xcccccccc); v = ((v >> 4) & 0x0f0f0f0f) | ((v << 4) & 0xf0f0f0f0); v = ((v >> 8) & 0x00ff00ff) | ((v << 8) & 0xff00ff00); v = ((v >> 16) & 0x0000ffff) | ((v << 16) & 0xffff0000); return v; }
输入两个数A和B,输出将A转换为B所需改变的二进制的位数。
首先,A异或B得到的是A和B中不相同位数组成的数,然后再求这个数二进制表示中1的个数,即为所求。
countOnes(A^B);
数组中只出现一次的数字
用到了n & (n - 1) 和 a ^ b ^ b = a ### 数组中,只有一个数出现一次,剩下都出现两次,找出出现一次的数 因为只有一个数恰好出现一个,剩下的都出现过两次,所以只要将所有的数异或起来,就可以得到唯一的那个数。 参考文章:http://zhedahht.blog.163.com/blog/static/2541117420071128950682/ ### 数组中,只有一个数出现一次,剩下都出现三次,找出出现一次的数 因为数是出现三次的,也就是说,对于每一个二进制位,如果只出现一次的数在该二进制位为1,那么这个二进制位在全部数字中出现次数无法被3整除。
膜3运算只有三种状态:00,01,10,因此我们可以使用两个位来表示当前位%3,对于每一位,我们让Two,One表示当前位的状态,B表示输入数字的对应位,Two+和One+表示输出状态。
参考文章:http://zhedahht.blog.163.com/blog/static/25411174201283084246412/
数组中,只有两个数出现一次,剩下都出现两次,找出出现一次的数
有了第一题的基本的思路,我们可以将数组分成两个部分,每个部分里只有一个元素出现一次,其余元素都出现两次。那么使用这种方法就可以找出这两个元素了。 不妨假设出现一个的两个元素是x,y,那么最终所有的元素异或的结果就是res = x^y。并且res!=0,那么我们可以找出res二进制表示中的某一位是1。对于原来的数组,我们可以根据这个位置是不是1就可以将数组分成两个部分。x,y在不同的两个子数组中。而且对于其他成对出现的元素,要么在x所在的那个数组,要么在y所在的那个数组。
位操作实现加减乘除运算
//加法 int BinaryAdd(int a, int b) { int carry, add; do { add = a ^ b; //该操作得到本位的加法结果 carry = (a & b) << 1; //该操作得到该位对高位的进位值 a = add; b = carry; } while (carry != 0); //循环直到某次运算没有进位,运算结束 return add; } //减法 int BinarySub(int a, int b) { return BinaryAdd(a, BinaryAdd(~b, 1)); } /*乘法 该过程中的bit_map是为了快速得到乘法过程中某位相乘的中间结果S[i] 需要位移的位数。bit_map的键值是2^0, 2^1,2^2, ……之类的数,对应的 值是0,1,2,……(即需要位移的位数)。 */ int BinaryMultiply(int a, int b) { bool neg = (b < 0); if(b < 0) b = -b; int sum = 0; map<int, int> bit_map; for(int i = 0; i < 32; i++) { bit_map.insert(pair<int, int>(1 << i, i)); } while(b > 0) { /* b & ~(b - 1)可以得到乘数b的二进制表示中最右侧1的位置 last_bit记录被乘数a需要位移的位数 */ int last_bit = bit_map[b & ~(b - 1)]; //将得到的乘法结果全部相加即为最后结果 sum += (a << last_bit); b &= b - 1; //每次将b的二进制表示的最右侧1去掉用于下一次乘法 } if(neg) sum = -sum; return sum; } //除法 int BinaryDivide(int a, int b){ bool neg = (a > 0) ^ (b > 0); if(a < 0) a = -a; if(b < 0) b = -b; if(a < b) return 0; int msb = 0; //msd记录除数需要左移的位数 for(msb = 0; msb < 32; msb++) { if((b << msb) >= a) break; } int q = 0; //记录每次除法的商 for(int i = msb; i >= 0; i--) { if((b << i) > a) continue; q |= (1 << i); a -= (b << i); } if(neg) return -q; return q; }
reference:
- 优秀程序员不得不知道的20个位运算技巧http://blog.csdn.net/zmazon/article/details/8262185
- 位操作实现加减乘除四则运算http://blog.csdn.net/u013074465/article/details/42680239
