假装体现主题的标题
这篇文章的内容是我从 剑指Offer对答如流系列 - 二进制中 1 的个数 中抽出来的,唉。写个位运算的解析,写入迷了,对位运算的讨论有点多了,干脆拿出来单独成一片文章算了。
一、盘点位运算的骚操作
位运算 效率高,速度快,是开车老司机出行游玩,登山必备。
前三条的运用参考 剑指Offer对答如流系列 - 二进制中 1 的个数 ,下面出发吧。
(1)移位运算(左移or右移)快于乘除
如果只针对2 进行乘除 强烈建议采用这种方法。
(2)一个整数减去1,再和原整数做与运算,会把该整数最右边的1变成0
判断一个数是否是2的次幂 也可以用这种方法呀。 n&(n-1)为0 说明这个数就是2的次幂
(3)通过右移判断二进制某位是0还是1
剑指offer的拿到面试题体现的思想与这个一致,不过是用1进行左移实现的,思想保持一致。
(4)判断奇偶数
二进制中,末位是0→偶数,末位是1→奇数。
n&1=0 — 偶数
n&1=1 — 奇数
求求你,看完这篇文章后,判断奇偶不要再取余了
(5)两个相同的数异或的结果是 0,一个数和 0 异或的结果是它本身
如果给你一组整型数据,这些数据中,其中有一个数只出现了一次,其他的数都出现了两次,让你来找出这只出现一次的数 。直接一个for循环,所有的值进行异或,结果就是那个只出现一次的数。
(6)两个数的交换
高逼格啊,相信你会在某些框架的源码中看到的
number1 = number1 ^ number2;
number2 = number1 ^ number2;
number1 = number1 ^ number2;
(7)不用判断语句,求整数的绝对值
正负通用 : i^(i>>31) + i>>>31
负数专用 : i^-1 + 1
相信我 这个看框架源码 会遇见的。
有读者提问这里不太明白,我这里说一下原理:
- >>是带符号的右移 >>>是不带符号的右移(也叫逻辑右移)
- 将一个整型整数x,带符号右移31位,则结果要么是0,要么是-1。如果是0,则x为正数,为-1则x为负数。
假定x右移31位后的结果是y吧,下面我们将x与y作异或运算,考虑到上面的原理:
- 如果y的值为0,x的值为正数,x^y的值为x本身。 x >>> 31 的值为0(正数二进制最高位为0) 它们相加的结果仍是x
- 如果y的值为-1, x的值就是负数,x^y的值为 x的反码。比如-11的反码是10,这个时候x>>>31的值为1(负数二进制最高位为1),它们相加的结果是11,说白了就是求补码。
public static void main(String[] args) {
int i=-11,j=11;
// 正负通用
System.out.println(i+"的绝对值是"+((i^(i>>31))+(i>>>31)));
System.out.println(j+"的绝对值是"+((j^(j>>31))+(j>>>31)));
//已知i是负数的用法
System.out.println(i+"的绝对值是"+((i^-1)+1));
}
二、位运算高阶操作
上面举的用起来已经很欢乐了。要不要见一下一个大厂的专门考察位运算的面试题?
不用 + - * / 实现 加减乘除
(一)加法
对于位运算,还有一些你需要了解的基本性质
(1)二进制位异或运算相当于对应位相加,不考虑进位
1 ^ 1 = 0 ---> 1 + 1 = 0 (当前位值为0,进一位)
1 ^ 0 = 1 ---> 1 + 0 = 1 (当前位值为1)
0 ^ 0 = 0 ---> 0 + 0 = 0 (当前位值为0)
(2)二进制位与运算相当于对应位相加之后的进位
1 & 1 = 1 ---> 1 + 1 = 0 (当前位的值进一位)
1 & 0 = 0 ---> 1 + 0 = 1 (当前位的值不进位)
0 & 0 = 0 ---> 0 + 0 = 0 (当前位的值不进位)
(3)两个数相加:对应二进制位相加的结果 + 进位的结果
3 + 2 --> 0011 + 0010 --> 0011 ^ 0010 + ((0011 & 0010) << 1) ---> (0011 ^ 0010) ^ ((0011 & 0010) << 1)
当进位之后的结果为0时,相加结束
实现如下:
int add(int a,int b){
int s;
int c;
while(b != 0){
// 获得求和位
s = (a^b);
// 获得进位,然后需要向做移动一位表示进位
c = ((a&b)<<1);
a = s;
b = c;
}
return a;
}
换成递归的写法会更清晰点
int add(int a,int b){
// 假如进位为0
if(b ==0){
return a;
}
// 获得求和位
int s = a^b;
// 获得进位,然后需要向做移动一位表示进位
int c = ((a&b)<<1);
return add(s,c);
}
(二)减法
本质上是使用加法来做的,只是加了一个负数嘛
首先我们得先将被减数取反(a取反就是~a+1,补码知道吧)
// 取得相反数
int adverse(int a){
return add(~a,1);
}
// 减法函数 其中add就是前面的加法函数
int subtract(int a,int b){
return add(a,adverse(b));
}
(三)乘法
这个本质上也是靠加法来实现的,比如说6 * 100, 不就6个100相加吗?
不过我们要多考虑的事情是 正负的 处理
// 负数返回-1,正数返回0
int getsign(int i){
return (i >> 31);
}
// 如果为负数,则进行取反
int toPositive(int a) {
if (a >> 31 == -1)
// 进行取反
return add(~a, 1);
else
return a;
}
然后就用加法实现呗
int multiply(int a, int b){
boolean flag = true;
// 如果相乘为正数,flag为false
if (getsign(a) == getsign(b))
flag = false;
// 将a取正数
a = toPositive(a);
b = toPositive(b);
int re = 0;
while (b!=0) {
// 相加
re = add(re, a);
// b进行次数减一
b = subtract(b, 1);
}
// 假如结果是负数,则进行取反
if (flag)
re = adverse(re);
return re;
}
但是 若是单纯靠加法来实现,数字比较大的时候感觉实在太慢了。
考虑我们现实生活中手动求乘积的过程,这种方式同样适用于二进制,下面我以13*14为例,演示如何用手动计算的方式求乘数和被乘数绝对值的乘积。
步骤和十进制的类似
(1) 判断乘数是否为0,为0跳转至步骤(4)
(2) 将乘数与1作与运算,确定末尾位为1还是为0,如果为1,则相加数为当前被乘数;如果为0,则相加数为0;将相加数加到最终结果中;
(3) 被乘数左移一位,乘数右移一位;回到步骤(1)
(4) 确定符号位,输出结果;
实现的话,就这样
int multiply(int a, int b) {
boolean flag = true;
// 如果相乘为正数,flag为false
if (getsign(a) == getsign(b))
flag = false;
// 将a取正数
a = toPositive(a);
b = toPositive(b);
int re = 0;
while (b!=0) {
// 假如b的最后一位为1 不就是奇偶的判断嘛 上面骚操作提过了 别问 问就是看上面
if((b&1) == 1){
// 相加
re = add(re, a);
}
b = (b>>1);
a = (a<<1);
}
// 假如结果是负数
if (flag)
re = adverse(re);
return re;
}
(四)除法
除法运算很容易想到可以转换成减法运算,就是不停的用除数去减被除数,直到被除数小于除数时,此时所减的次数就是我们需要的商,而此时的被除数就是余数。
这里需要注意的是符号的确定,商的符号和乘法运算中乘积的符号确定一样,即取决于除数和被除数,同号为证,异号为负;余数的符号和被除数一样。
实现上与乘法第一种思路大同小异,但是效率低。
要想提高效率,可以参考乘法实现的第二种思路就是列竖式。这里就只提供第二种思路咯
int divide(int a,int b){
boolean flag = true;
// 如果相除为正数,flag为false
if (getsign(a) == getsign(b))
flag = false;
// 将a取正数
a = toPositive(a);
b = toPositive(b);
int re = 0;
int i = 31;
while(i>=0){
// 如果够减
// 不用(b<<i)<a是为了防止溢出
if((a>>i)>=b){
// re代表结果
re = add(re, 1<<i);
a = subtract(a, (b<<i));
}
// i减一
i = subtract(i, 1);
}
// 假如结果是负数
if (flag)
re = adverse(re);
return re;
}
done,我女朋友们看了都说好!
转载:https://blog.csdn.net/qq_42322103/article/details/104033181