神奇的位运算(bitwise trick)
来源:互联网 发布:mysql删除自增长 编辑:程序博客网 时间:2024/05/19 13:15
在计算机中,数据都以二进制补码的形式存储,根据这一特点,适当采用位运算(bitwise operation)可以很巧妙地解决问题,同时运算效率更高。时刻牢记,最大的负数是-1,在计算机中,它的存储形式是全1。
位运算符
- 左移<< 和 右移>>
左移相当于乘以2,友谊相当于除以2.在计算机中,位运算比乘法、除法运算要快得多,所以适当采用移位运算取代乘除运算,能够提高运算效率。右移时要注意符号位的扩展。
- 按位与&
&运算的特点是可以将某些位置为0。
&运算也可用于提取某些特定的位。做法是定义一个mask,mask中对应要提取的位置为1,而其它位为0,将整数与mask做&运算,就可以提取出整数中相应的位。
- 按位或|
|运算的特点是,可以将指定的位元置为1。
- 按位异或^
以下运算的规律在解决问题时可能是会经常用到的。
x^x=0
bit^1=~bit x^(-1)=~x
bit^0=bit x^0=x
- 按位取反~
~运算的特点是将0和1点到。以下规律值得牢记,此规律对正整数或负整数都成立。
-x=~x+1
一些小技巧
位运算结合在一起,恶意巧妙地解决很多问题,下面就是一些bitwise trick。
- 交换两个int
void swap(int &x, int &y){ x = x^y; y = x^y; //(x^y)^y = x^(y^y) = x^0 = x; x = x^y; //(x^y)^x = (x^x)^y = 0^y = y;}
最早以前,我所知道的交换方法是下面这样的
void swap(int &x,int &y){ x = x+y; y = x-y; x = x-y;}
这样在大多数情况下也是可以的,但是不及位运算。首先,撇开运算速度不说,通过加减交换最大的隐患在于,如果x和y的值都很大,那么在计算x+y的时候,可能发生溢出,这在位运算中是绝对不会出现的。
- 判断一个数是不是2的幂
void PowerOf2(int x){ if (x == 0) return false; return x&(x-1) == 0;}
x&(x-1)
的作用是,使得x最右边的1变为0,x是2的幂,意味着x中只有一个1,所以经过这一运算后,x&(x-1)等于0。
还有一个特点需要注意的是,如果x是2的幂,那么x-1
的形式是,左边全为0,右边全为1。
另一个解为return x&(-x) ==x;
,但是具体怎么解,我还不明所以。
x&(x-1)
的作用不容小觑,我们可以用它来计算整数中1的个数,并借此来对整数做奇偶校验。
- 计算整数中1的个数
int CountOf1(int x){ int count=0; while(x) { count ++; x = x&(x-1); //将x最低位的1置为0 } return count;}
- 计算m和n中不同的位的个数
int diffmn(int m,int n){ int count=0; int x = m^n; //x中为1的位元代表了m和n中不同的位 while(x) { count ++; x = x&(x-1); } return count;}
- 整数加1和整数减1
int plus1(int x){ return -~x; //x+1 = ~(~x)+1 = -(~x)}
int minus1(int x){ return ~-x; //x = -(-x) = ~(-x)+1}
注意:通过上述方法计算整数的加1或减1,比直接计算是要慢的。不过,这两个例子充分说明了位运算可以做的事情是很多的。
老实说,这两个结果应该不是那么容易记清的吧,起码对我来说是这样的,牢记-x=~x+1
,并记住大概的形式,应该就能够通过简单的变换很快得出正确的结果的吧。
- 整数反号
void revsign(int x){ return ~x+1; //-x = ~x+1}
或
void revsign(int x){ return (x^(-1))+1; //(x^(-1))+1 = ~x+1 = -x}
显然,第二种方法是不如第一种方法的,还是那句话,通过推导,好好体会一下位运算的神奇效果吧。
- 判断一个数是否是奇数
bool isodd(int x){ return x&1}
偶数的判断当然也很简单了!
- 计算32位整数的绝对值
int abs(int x){ return (x&(x>>31))-(x>>31);}
首先需要注意的是右移时会扩展符号位。如果x是正数,那么x>>31=0
,毫无疑问,return
语句中的表达式结果就是x;如果x是负数,那么x>>31
的结果就是全1,也就是我们必须牢记的-1,于是
(x^(x>>31)) - (x>>31)=(x^(-1)) - (-1)=~x + 1=-x
所以,(x^(x>>31))-(x>>31)
就是我们要求的x的绝对值。
- 求a和b的最大值
int max(int a,int b){ int maxab = b&((a-b)>>31) | a&(~(a-b)>>31); return maxab;}
对于unsigned int,右移31位是一个很特别的操作,其作用是将所有的位都置为符号位的值,也就是说,正数移位后变为0,负数移位后变为-1。所以,如果a<b
,那么(a-b)>>31
结果就是-1,而~(a-b)>>31
结果就是0,最终maxab = b
;反过来,如果a>b
,那么(a-b)>>31
结果是0,而~(a-b)>>31
结果是-1,最终maxab = a
。
需要注意的是,此处假定在32位机器上进行操作,因此移位的位数是31,为了保证表达式的通用性,可以通过sizeof(int)*8 - 1
来确定移位的位数。
下面是另外一种解法,成立的条件是true=1,false=0
。
int max(int a,int b){ int maxab = a^((a^b)&(-(a<b))); return maxab;}
如果a<b
,那么-(a<b)
就是-1,于是maxab = a^(a^b) = b
;如果a>b
,那么-(a<b)
就是0,于是maxab = a
。
可以看到,两种解法的核心都是将a、b的关系通过-1或0来间接表示,并利用表示结果作进一步的运算。
- 求a和b的最小值
int min(int a,int b){ int minab = a&((a-b)>>31) |b&(~(a-b)>>31); return minab;}
同样的,只要满足true=1,false=0
,也同样可以采用第二种方法来求最小值。
int min(int a){ int minab = b^((a^b)&(-(a<b))); return minab;}
推导过程与注意事项同求最大值的解法是基本相同的,在此不做赘述。
- 计算两个数的平均值
int mean(int x,int y){ return (x&y)+((x^y)>>1);}
我们知道,计算两个数的平均值,最直接的想法就是(x+y)/2
,表达式就是通过位运算实现了这一想法。表达式的核心思想是,将x+y
分解成两部分,一部分是x和y相同的位,一部分是x和y不同的位,前者通过&运算完成相加(由于没有进位,所以无需除以2),后者通过^运算完成相加,然后除以2得出平均值,最后,将两部分相加,就是所求的结果了。
同样的,通过位运算,可以成功规避相加溢出的问题。
在此,我们瞥见了位运算的一抹影子,你当然可以在其他很多地方领略到更多神奇之处,比如在演算法笔记中。
- 神奇的位运算(bitwise trick)
- 神奇的位运算
- 神奇的位运算
- 神奇的位运算
- 神奇的位运算
- 神奇的位运算
- 神奇的位运算
- Java的位运算(bitwise operators)
- 神奇的C++位运算& | << >> ^ ~ %
- 位运算trick
- 位运算符 Bitwise Operators
- python: Bitwise Operators (位运算)
- 【收集向】位操作技巧 bitwise operation trick
- zoj 3432 神奇的位运算。。
- [短码基础] 神奇的位运算
- 算法之神奇的位运算
- 神奇的C语言二十一:谈谈位运算
- [leetcode] Bitwise AND of Numbers Range(位运算)
- linux 安装 gcc
- Oracle批量执行SQL文件
- 使用mybatis-generator-core实现自动创建项目
- OpenCv图像叠加时png图片的透明部分无法透明的解决办法
- 日期函数 MONTHS_BETWEEN
- 神奇的位运算(bitwise trick)
- 岩棉板屋面保温系统
- 初识——Vim
- 一句话理解闭包
- 玄武岩棉板和矿渣岩棉板性能对比
- Unicode字符集和多字节字符集关系
- java中泛型小结
- 常见报错
- [华为机试练习题]11.最大递减数