位运算
大家好,我是魔笑,我又来了,今天给大家分享一篇,位运算。下面是这篇文章的三大章节
一,位运算基础知识
二,位运算的常见用法
三,位运算算法题
一,位运算基础知识
了解位运算,那么我们不得不了解,葫芦娃七兄弟了
什么是葫芦娃七兄弟,大家第一个反映肯定是,动画片,金刚葫芦娃,葫芦娃,救爷爷。哈哈,经典的同年的动画片
下面的葫芦娃起兄弟,是大家学习位运算不得不掌握的基本知识了:
运算符 | 名称 | 英文 | 含义 |
& | 与 | and | 同为1则为1,否则就是0 |
| | 或 | or | 只要有一个是1,则结果就为1 |
^ | 异或 | xor | 相同则是0,不同则为1 |
~ | 取反 | not | 0变成1,1变成0 |
<< | 左移 | shi | a<<b,把二进制a左移b位,后面补0 |
>> | 右移 | shr | a>>b,把二进制a右移b位,高位补原符号位(0或1) |
>>> | 无符号右移 | unsigned right shift | a>>>b,把二进制a右移b位,高位补0 |
了解位运算,不得不了解,二进制
1,什么是原码,反码,补码?
补码:首先,我得知道,计算机中所有数据得存储和运算都是以“二进制补码”的形式进行的
原码:我们可阅读的二进制原码
反码:从原码到补码的过程码
1.1,5的原码,反码,补码
//5的8位2进制原码 //5的8位2进制反码 //5的8位2进制补码
0000 0101 0000 0101 0000 0101
//5的32位2进制
0000 0000 0000 0000 0000 0000 0000 0101
总结:正数的原码,反码,补码,都是一样的
1.2,-5的原码,反码,补码
//-5的8位2进制原码 //-5的8位2进制反码 //5的8位2进制补码
1000 0101 1111 1010 1111 1011
//-5的32位2进制原码
1000 0000 0000 0000 0000 0000 0000 0101
//-5的32位2进制反码
1111 1111 1111 1111 1111 1111 1111 1010
//-5的32位2进制补码
1111 1111 1111 1111 1111 1111 1111 1011
总结:从5的原码和-5的原码对比,最高位是符号位,1是负数,0是正数
1.3,补码转原码
补码转反码:
-5的补码:1111 1011
-5反码:1111 1010 是补码减1
反码转补码:
-5的反码是: 1111 1010
-5的原码:1000 0101 反码符号位不变,数值位取反
总结一:补码转成反码(反码-1),反码转成原码(符号位不变,数值位取反)。
总结二:原码转成反码(符号位不变,数值位取反),反码转成补码(反码+1)。
2,将二进制换算成10进制
如:0000 1010 的十进制为10
0乘2的0次方+1乘2的1次方+0乘2的2次方+1乘2的3次方+....+=10
二,八葫芦的常见用法
1,&的常用用法
1.1,判断奇偶性
5&1=1 2&1=0
------------ ------------
0000 0101 0000 0010
& 0000 0001 & 0000 0001
------------ ------------
0000 0001 0000 0000
总结:奇数&1等于1,偶数&1等于0
1.2,对x取模(取余数)x%y==x&(y-1)
5%4=1 17 % 16 = 1
------------ ---------------
0000 0101 0001 0001
& 0000 0011 & 0000 1111
------------ ---------------
5&(4-1)=1 17&(16-1)=1
总结:
x%y==x&(y-1)
1.3,x&-x=最低位1的权值
5& -5 = 1 6& -6 = 2
------------ -------------
0000 0101 0000 0110
& 1111 1011 & 1111 1010
------------ --------------
0000 0001 0000 0010
x&-x=最低位1的权值
2,|(或)的常用用法
2.1,可用于设置权限,状态机,位图
0000 0101 0000 0101
| 1111 1011 | 0001 0000
------------- ------------
1111 1111 0001 0101
合并1 将某个位置设置成1
例如:假设,1个用户,权限字段的二进值表示权限,数字12(0000 1100),分别有八个权限,1代表开启,0代表关闭,顺序分别是A,B,C,D,E,F,G,H权限,我们将D权限开启,12|16=28
0000 1100
| 0001 0000
---------
0001 1100
总结,总共开启了D权限
3,^(异或),~(取反)的常用用法
3.1,偶数+1,奇数-1
6^1=7 5^1=4
------------ -------------
0000 0110 0000 0101
^ 0000 0001 0000 0001
------------ -------------
0000 0111 0000 0100
3.2,x ^ -1=~x
0000 0001=> 1 ~ 1 = ~ 0000 0001 = 1111 1110 = -2
^1111 1111=> -1
---------------
1111 1110 =>-2 <=> ~1
3.3,求相反数(x ^ -1)+1=-x 或者 ~x+1=x
0000 0001=> 1 ~ 1 <=> ~ 0000 0001 <=> 1111 1110 <=> -2
^1111 1111=> -1 ~ 1+1 <=> -2+1 <=> -1
---------------
1111 1110 =>-2+1 <=> -1
3.4,加密文件,交换两个变量的值 a^b^b=a
0000 0101 0000 1100
^ 0000 1001 ^ 0000 1001
------------ => ------------
0000 1100 0000 0101
结论:5^9^9 <=> 9
加密文件的例子:
public static void lock() {
int param = 18;
String src = "d:\\test.txt";
String dest = "d:\\test2.txt";
try {
InputStream is = new FileInputStream(new File(src));
OutputStream os = new FileOutputStream(new File(dest));
byte[] bytes = is.readAllBytes();
for (int i = 0; i < bytes.length; i++) {
//加密,将文件^param,解密的时候再^param,就可以了
bytes[i] = (byte)(bytes[i] ^ param);
}
os.write(bytes);
os.close();
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
4,>>右移的常用用法
4.1,判断正负,负数>>31=-1,正数>>31=0
-5 => 1111 1011
---------------------------
-5>>31 => 1111 1111 =>转化成原码是:1000 0001 =>-1
总结:负数>>31=-1
5 => 00000101
-------------------
>>31 => 0000 0000
总结:正数>>31=0
三,位运算算法题
1,题目
编写一个函数,返回一个整数二进制表示中,位‘1’的个数
例如:
输入:n=8(0000 0000 0000 0000 0000 0000 0000 1000)
输出:1
解法一:
1,int类型是4个字节,是32位,所以我们得用32位二进制计算
2,假设n=5,5的2进制是:0000 0000 0000 0000 0000 0000 0000 0101,1的二进制是:0000 0000 0000 0000 0000 0000 0000 0001
3,所以我们可以用“n&(1<<i)”,1<<i(就是将1不断左移,后面的补0,超过的舍掉),当1的二进制位为1的那位,对应5的二进值为1则为1,否则为0,当1不断右移,就能计算出n有多少1了,如下:
1<<0,移动0位
0000 0000 0000 0000 0000 0000 0000 0101
& 0000 0000 0000 0000 0000 0000 0000 0001
-------------------------------------------
0000 0000 0000 0000 0000 0000 0000 0001
1<<1,移动1位
0000 0000 0000 0000 0000 0000 0000 0101
& 0000 0000 0000 0000 0000 0000 0000 0010
-------------------------------------------
0000 0000 0000 0000 0000 0000 0000 0000
1<<2,移动2位
0000 0000 0000 0000 0000 0000 0000 0101
& 0000 0000 0000 0000 0000 0000 0000 0100
-------------------------------------------
0000 0000 0000 0000 0000 0000 0000 0100
代码:
public int hammingWeight(int n) {
int count = 0;
for (int i = 0; i < 32; i++) {
if ((n & (1 << i)) != 0) {
count++;
}
}
return count;
}
解法二:
* 基于这样的事实:X与X-1相与得到的最低位永远是0
* 减1操作将最右边的符号从0变到1,从1变到0,与操作将会移除最右端的1。
* 如果最初X有N个1,那么经过N次这样的迭代运算,X将减到0
public int hanmingWeight(int n) {
int count = 0;
while (n != 0) {
// 每次操作都将移除最右端的1
n &= n - 1;
count++;
}
return count;
}
.
2,题目
有序整数数组中有一个数出现1次,其他数都出现两次,找到那个出现一次的数,
示例:
输入:[2,2,3,3,6,8,8,9,9]
输出:6
解法一
这个我们肯定想到用交换a^b^b=a
public static int findTheOnlyOneNumber(int[] arrays) {
int s=0;
for(int n:arrays){
s=s^n;
}
return s;
}
解法二
用二分发,和用奇数&1等于1,偶数&1等于0,判断奇偶
public int findTheOnlyOneNumber(int[] arrays, int start, int end) {
if (start >= end) {
return arrays[start];
}
//mid为奇数和左边比较,mid为偶数和右边比较
int mid = (start + end) / 2;
int left = mid, right = mid + 1;
if ((mid & 1) == 1) {
//为奇数则mid-1和mid比
left = mid - 1;
right = mid;
}
if (right > end || left < start) {
return arrays[mid];
} else if (arrays[left] == arrays[right]) {
//左边正常,找右边
return findTheOnlyOneNumber(arrays, right + 1, end);
} else {//右边正常,找左边
return findTheOnlyOneNumber(arrays, start, left);
}
}