算法通关村——位运算基础

位运算作为计算机的核心基础,数据的表示和计算几乎都少不了,在JVM以及很多高性能代码李大量使用,甚至很多算法本身就是基于位运算进行的。许多算法看起来和位运算无关,但是用位运算操作优化一下,性能会提升很多,所以位运算的问题值得好好学习。
学习位运算之前,我们要先明确计算机原码、反码、补码的概念和表示方法,之后介绍位运算相关的问题。

1 数字在计算机中的表示

原码 就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值,比如如果是8为二进制:

[+1]原 = 0000 0001
[-1]原 = 1000 0001

第一位为符号位,所以8为二进制数的取值范围就是:
[1111 1111, 0111 1111], 即[-127 , 127]
反码 的表示方法是:正数的反码是其本身,而负数的反码是在其原码的基础上,符号位不变,其余各位取反。例如:

[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原 = [11111110]反

可见如果一个反码表示的是负数,人脑无法直观的看出来它的数值,通常要将其转
成原码再计算。
在应用中,因为 补码 能保持加和减运算的统一,因此应用更广,其表示方法是:

  • 正数的补码就是其本身;
  • 负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后+1(即在反码的基础上+1)。

[+1] = [00000001]原 = [00000001]反 = [00000001]补
[-1] = [10000001]原 = [11111110]反 = [11111111]补

2 位运算规则

本节内容应该大部分同学都学过,但是请再认真思考一遍,因为大量算法解决思路都是从这里引申出来的。
位运算主要有:与、或、异或、取反、左移和右移,其中左移和右移统称移位运算,移位运算又分为算术移位和逻辑移位。

2.1 与、或、异或和取反

与运算的符号是 &,运算规则是:对于每个二进制位,当两个数对应的位都为 1 时,结果才为 1,否则结果为 0。

0 & 0=0
0 & 1=0
1 & 0=0
1 & 1=1

或运算的符号是 |,运算规则是:对于每个二进制位,当两个数对应的位都为 0 时结果才为 0,否则结果为 1。

0 ∣ 0=0
0 ∣ 1=1
1 ∣ 0=1
1 ∣ 1=1

异或运算的符号是 ⊕(在代码中用∧ 表示异或),运算规则是:对于每个二进制位当两个数对应的位相同时,结果为 0,否则结果为 1。

0⊕0=0
0⊕1=1
1⊕0=1
1⊕1=0

取反运算的符号是 ∼,运算规则是:对一个数的每个二进制位进行取反操作,0 变
1,1 变成 0。

∼0=1
∼1=0

以下例子显示上述四种位运算符的运算结果,参与运算的数字都采用有符号的 8 位进制表示。

  • 46 的二进制表示是 00101110,51 的二进制表示是 00110011。考虑以下位运算结果。
  • 46&51的结果是34,对应的二进制表示是00100010。
  • 46|51 的结果是63,对应的二进制表示是00111111。
  • 46⊕51 的结果是29,对应的二进制表示是00011101。
  • ∼46 的结果是−47,对应的二进制表示是11010001。
  • ∼51 的结果是 −52,对应的二进制表示是 11001100。

2.2 移位运算

移位运算按照移位方向分类可以分成左移和右移,按照是否带符号分类可以分成算术移位和逻辑移位。原始:0000 0110 6
右移一次:0000 0011 3 相当于除以2
左移一次:0000 1100 12 相当于乘以2

左移运算的符号是 <<,左移运算时,将全部二进制位向左移动若干位,高位丢弃
低位补 0。对于左移运算,算术移位和逻辑移位是相同的。

右移运算的符号是 >>。右移运算时,将全部二进制位向右移动若干位,低位丢弃
高位的补位由算术移位或逻辑移位决定:

  • 算术右移时,高位补最高位;
  • 逻辑右移时,高位补 0。
    以下例子显示移位运算的运算结果,参与运算的数字都采用有符号的 8 位二进制表
    示。
  • 示例1:29 的二进制表示是 00011101。29左移 2 位的结果是 116,对应的二
    进制表示是 01110100;29 左移 3 位的结果是 −24,对应的二进制表示是
    11101000。
  • 示例2:50的二进制表示是 00110010。50 右移 1 位的结果是 25,对应的二
    制表示是 00011001;50 右移 2 位的结果是 12,对应的二进制表示是
    00001100。对于 0和正数,算术右移和逻辑右移的结果是相同的。
  • 示例3:-50的二进制表示是 11001110(补码)。-50 算术右移 2 位的结果是
    −13,对应的二进制表示是 11110011;−50 逻辑右移 2位的结果是 51,对应
    的二进制表示是 00110011。

右移运算中的算术移位和逻辑移位是不同的,计算机内部的右移运算采取的是哪一
呢?

  • 对于 C/C++ 而言,数据类型包含有符号类型和无符号类型,其中有符号类型
    用关键字signed 声明,无符号类型使用关键字 unsigned 声明,两个关键字都
    不使用时,默认是有符号类型。对于有符号类型,右移运算为算术右移;对于
    符号类型,右移运算为逻辑右移。
  • 对于 Java 而言,不存在无符号类型,所有的表示整数的类型都是有符号类型
    因此需要区分算术右移和逻辑右移。在Java 中,算术右移的符号是 >>,逻辑
    移的符号是 >>>。

2.3 位运算常用技巧

1. 获取(判断第i位是否不为0)
该方法是将1左移i位,得到形如00010000的值。接着堆这个值与num执行“位于”操作,从而将i位之外的所有位清零,最后检查该结果是否为零。不为零说明i为为1,否则为0.代码如下:

boolean getBit(int num, int i){
        return ((num & (1<<i)) != 0);
    }

2.设置
setBit先将1左移i位,得到形如00010000的值,接着堆这个值和num执行”位或“操作,这样只会改变i位的数据。这样除i位外的位均为零,故不会影响num的其余位。代码如下:

int setBit(int num, int i){
        return num|(1<<i);
    }

3. 清零
该方法与setBit相反,首先将1左移i位获得形如00010000的值,对这个值取反进而得到类似11101111的值,接着对该值和num执行”位与“,故而不会影响到num的其余位,只会清零i位。

int clearBit(int num, int i){
        int mask = ~(1<<i);
        return num & mask;
    }

4.更新
这个方法是将setBit和clearBit合二为一,首先用诸如11101111的值将num的第i位清零。接着将待写入值v左移i位,得到一个i位为v但其余位都为0的数。最后对之前的结果执行”位或“操作,v为1这num的i位更新为1,否则为0:

 int updateBit(int num, int i, int v){
        int mask = ~(1 << i);
        return (num & mask) | (v << i);
    }

上面这几种方法要透彻理解,不要死记硬背,很多棘手问题就能逐步拆解出解决的方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值