持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
前言
最近在看《剑指offer》的时候,发现位运算的执行效率和性能都很友好,使用位运算的代码看起来也更加整洁。之前也有用过一些位运算的操作,现在这篇文章,来整理总结一下知识点啦。
一、位运算知识介绍
【&>> ^<< |~】可能没了解位运算的同学会以为这是颜文字表情包。在计算机里位运算和四则运算一样的重要,其他编程语言也都有位运算,那么下面就和大家一起感受一下这些表情包符号的魅力哈哈哈。。
1、各个符号的意思
首先,先看看下表格,了解每个符号的作用。
符号 | 描述 | 运算规则 | 示列 | 结果 |
---|---|---|---|---|
& | 与 | 两个位都为1时,结果才为1 | 4 & 6 = 100 & 110 | 100 = 4 |
| | 或 | 两个位都为0时,结果才为0 | 4 | 6 = 100 | 110 | 110 = 6 |
异或 | 两个位相同为0,相异为1 | 4 ^ 6 = 100 ^ 110 | 010 = 2 | |
~ | 取反 | 0变1,1变0 | ~1=0 | ~0 =1 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0 | 4 << 1 | 1000 = 8 |
>> | 右移 | 各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移) | 4 >> 1 | 10 = 2 |
2、具体示例
通常来讲,与(&) ,或(|),异或(^)
的使用可能会更多,下面表格再具体列出,二进制中它们的表现,
下列以表展示,
表-----------
与(&) | 0 & 0 =0 | 1 & 0 =0 | 0 & 1 =0 | 1 & 1 =1 |
---|---|---|---|---|
或(|) | 0 | 0 =0 | 1 | 0 =1 | 0 | 1 = 1 | 1 | 1 = 1 |
异或(^) | 0 ^ 0 =0 | 1 ^ 0 =1 | 0 ^ 1 =1 | 1 ^ 1 =0 |
图---------------
3、用途介绍
1、与运算的用途:
1)清零
如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。
2)取一个数的指定位
比如取数 X=1010 1110 的低4位,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位与运算(X&Y=0000 1110)即可得到X的指定位。
3)判断奇偶
只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数。
2、或运算的用途:
1)常用来对一个数据的某些位设置为1
比如将数 X=1010 1110 的低4位设置为1,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位或运算(X|Y=1010 1111)即可得到。
3、异或运算的用途:
1)翻转指定位
比如将数 X=1010 1110 的低4位进行翻转,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行异或运算(X^Y=1010 0001)即可得到。
2)与0相异或值不变
例如:1010 1110 ^ 0000 0000 = 1010 1110
3)交换两个数
见目录第四部分、在程序中使用位运算
二、位运算运行性能好
因为性能好,所以一些框架里经常可以看到会使用位运算【&>> ^<< |】,来代替加减乘除求余数等运算【+-*/%】,下图纯属好看
pie title 为什么位运算性能好
"Vue" : 386
"React" : 85
"jQuery" : 15
因为位运算是汇编级的运算速度,操作的是二进制数值,因此框架源码为提高性能会采用位运算。不过在实际工作中,少量的计算的话速度优化不会很明显,如果考虑代码可读性等方面的话,就以实际需要使用就好了。
三、面试题和算法题中的位运算
下面我们以《剑指offer》和leetcode中几道常见的算法考察题来巩固上面学习的位运算知识
1、二进制中1的个数
题目解答
开始,判断二进制的最右边一位是否为 1。是 1 就计数加一。n 向右移动一位(除于2),下一轮判断时就是判断原始n的倒数第二位了, 这种解法是每次改变了n的大小,
n为25时二进制表示为11001,运算如下:
11001->1100->110->11->1->0结束,1的个数为3
function NumberOfOne(n) {
let count = 0;
while (n) {
if (n & 1) { // (1) 判断二进制的最右边一位是否为 1。
count++; // (2) 是 1 就计数加一
}
n = n >> 1; // (3)n 向右移动一位(除于2),下一轮判断时就是判断原始n的倒数第二位了
}
return count;
}
上面的程序中,如果遇到负数,一直右移运算的话可能会导致死循环,因为负数和整数的的边界值是这样的,正数【1、0X7FFFFFFF】
, 负数【0X80000000、0XFFFFFFFF】
考虑负数后优化 使用flag变量来和n 相与,同样当n为25,11001时 步骤如下:
11001 & 1 =1, 11001 & 10 =0, 11001 & 100 =0, 11001 & 1000=1, 11001 & 10000 =1, 结束,得到1的个数为3
// 如果输入的n 是负数的话
function NumberOfOne_planB(n) {
let count = 0, flag = 1
while (flag) {
if (n & flag) count++;
flag = flag << 1
}
return count;
}
2、判断一个整数是否为2的整数次方
写一个函数,用一条语句判断一个整数是否为2的整数次方,
例如 4 、 8、16、32、64返回true. 92,74返回false 解析
众所周知,2 的整次方转为二进制的话,它就是一个 1,后面几次方就更着几个零,如: 2的三次方8的二进制表示为1000,2的四次方16的二进制表示为10000
,这样我们结合与运算&,判断它和它减一,进行相与是否为了零, 为什么是n-1和n相与呢? 因为众所周知,例如7 = 8-1 = 111,相与的时候往前面补零就是0111 15 =16-1 = 01111
function isTwoPower(n) {
return ((n - 1) & n) ? false : true; // 有值就是非零,就是false,零的话就是true
}
// test
console.log(isTwoPower(64));
console.log(isTwoPower(35));
console.log(isTwoPower(4));
console.log(isTwoPower(128));
3、判断m和n的二进制转换需几步
解析
- (1) 获取m、n 的异或运算结果
- (2) 返回m、n异或运算结果中 1 的个数就需要改变的位数。
function m_To_n_NeedStep(m, n) {
let res = m ^ n // (1) 获取m、n 的异或运算结果
let count = 0
while (res) {
if (res & 1) count++;
res = res >> 1
}
return count // (2) 返回m、n异或运算结果中 1 的个数就需要改变的位数。
}
console.log(m_To_n_NeedStep(10, 13)); // 3 1010 -> 1101 改动三位
console.log(m_To_n_NeedStep(4, 6)); // 1 100 -> 110 改动一位
4、不用四则运算实现两数相加函数
解析
发散性思维,不能使用+-*/
,那只能使用位运算了
function Add(num1, num2) {
let sum, carry
while (num2 != 0) {
sum = num1 ^ num2
carry = (num1 & num2) << 1
num1 = sum
num2 = carry
}
return num1
}
// test
console.log(Add(10, 82));
console.log(Add(0, -2));
console.log(Add(-10, 2));
四、在程序中使用位运算
1、去除小数点
- 使用~~
let b = 12.22
console.log(~~b); // 12
- 使用>> 0
let c = 3.14
console.log(c >> 0); // 3
2、位运算代替乘除法
- 除法
let d = 24
console.log(d >> 1); // 12
- 乘法
let f = 60
console.log(f << 1); //120
3、判断奇数偶数
二进制中,最后一位数是1就是奇数,是零就是偶数
if (n & 1) 奇数
else 偶数
// 同
if(n % 2 != 0) j奇数
else 偶数
4、不增加变量实现两数交换
使用四则运算的实现方式
function swap_planA(a, b) {
console.log(a, b);
a = a + b
b = a - b
a = a - b
console.log(a, b);
}
使用位运算之异或运算的方式
function swap_planB(a, b) {
console.log(a, b);
a = a ^ b
b = a ^ b
a = a ^ b
console.log(a, b);
}
五、总结
地址
代码和解析放在了这个仓库里啦,适龄小伙伴们,有兴趣的话,可以一起来分享和贡献代码到这个仓库。
小结
- theme: awesome-green【初夏主题的感觉吧】 我们先介绍了每个位运算符的含义,以图表的形式展示,然后从算法题和面试题中巩固对位运算的认识,接着我们看了几个在程序中用到的位运算妙招,相信读完上面的内容,我们对位运算有了较好的认识了。那就完成一些下面的流程图吧哈哈哈。
graph TD
点赞 --> 收藏--> 评论
【剑指offer】