一. 算法基础知识

59 阅读10分钟

二进制

简单了解二进制

为什么计算机中是二进制

计算机内部只有电路开关,开关有两个状态,开和关,这对应二进制中的1和0。这样,用开关的状态就可以存储和处理信息。使用二进制后,电子组件比如晶体管仅需处理两种状态,开和关,相对较为稳定。 并且二进制逻辑(与、或、非等)容易实现并且是构建更复杂计算操作的基础。这些逻辑门是构建所有计算机程序和操作的基石。

什么是二进制

十进制使用0到9共十个数字,而二进制只使用0和1两个数字。 参考我们数据的 10 进制,987 就是。 所以 2 进制中的 10 就是 ,换算为十进制就是 2。 再举些例子

  • 十进制的1在二进制是1。
  • 十进制的2在二进制是10(意味着1个2和0个1)。
  • 十进制的3在二进制是11(意味着1个2和1个1)。

二进制的正负数表达

拿四位二进制来举例。_ _ _ _ 我们把最高位也就是第一位用来表示符号,0 代表非负数,1 代表负数。

非负数

所以当第一位为 0 时,0000 是最小的非负数,就是 0,0111是最大的非负数,1+2+4,也就是 7。

负数

负数的表达比较特殊,按照正数的逻辑,我们发现 1000 表示的是-0,这就不对了,0 和-0 是一样的,所以肯定不是这么表达的。

二进制中的负数表示:4位二进制补码

在二进制中表示负数,一种常用的方法是使用二进制补码(Two's Complement)。这种方法在计算机系统中非常普遍,因为它简化了包括正数和负数在内的加法运算。下面以4位二进制为例解释如何表示负数:

步骤一:了解4位二进制的范围
  • 在4位二进制中,你可以表示从00001111的数字。
  • 使用补码表示法,这些数字可以表示从-8到+7的范围。
步骤二:正数的表示
  • 正数在补码中的表示与普通的二进制相同。
  • 例如:十进制的+3在二进制中表示为0011
步骤三:负数的表示(计算补码)
  1. 取反:首先,将该数的正值写成二进制形式,然后对每一位进行取反(0变1,1变0)。
  2. 加一:然后,将取反后的结果加上1。

例如,让我们来表示十进制的-3

  1. 写出+3的二进制形式0011
  2. 取反:变成1100
  3. 加一1100 + 0001 = 1101

因此,-3在4位二进制补码表示法中为1101

步骤四:特殊的-8

在4位二进制补码中,最小的数字-8有一个特殊的表示:1000。注意,这个编码没有对应的正数形式,因为+8无法在4位二进制中表示。

通过这种方式,使用4位二进制补码可以有效地表示和处理正数和负数。这样的设计使得加法和减法都可以使用相同的电路进行运算,而不需要额外的逻辑来处理符号。

正数-1,再取反也是一样的,并且这样算的时候,-8 也是通用的,8 是 1000,减一后变成 0111,取反变成 1000

二进制负数转换为十进制

以 1101 为例:

  1. 方法一:先减一,再取反。1101 减一就是 1100,1100 取反就是 0011,0011 是 3,所以 1101 是-3
  2. 方法二:先取反,再加一。1101 取反就是 0010,0010 加一就是 0011,0011 是 3,所以 1101 是-3
  3. 方法三:4 位有符号的最小值是 -8, 1101 取后三位 101, 101 是 5, -8 + 5 = -3。

方法三是不是有点懵,其实是我觉得最好、最自然的一个方法,我们可以列出-8 到 7 的所有数: 如下表格,我们观察下,从-8 到-7 是不是+1,这是一定的,所以我们把-8 当成基准,去到第一位 1,后面三位就是 101 就是 5,-8 往上进 5 位就是 -3,可能我表达不是很清楚,可以自己体会一下,也正是这种设计,二进制的加法适用于正负数,很精妙。当然三种方法都可以。 简单点说,把符号位也就是最高位,当成-8,这些都能解释了。

十进制二进制表示
-81000
-71001
-61010
-51011
-41100
-31101
-21110
-11111
00000
10001
20010
30011
40100
50101
60110
70111

代码中怎么写二进制

代码计算

// 打印一个int类型的数字,32位进制的状态
// 左侧是高位,右侧是低位
public static void printBinary(int num) {
    for (int i = 31; i >= 0; i--) {
        // 下面这句写法,可以改成 :
        // System.out.print((a & (1 << i)) != 0 ? "1" : "0");
        // 但不可以改成 :
        // System.out.print((a & (1 << i)) == 1 ? "1" : "0");
        // 因为a如果第i位有1,那么(a & (1 << i))是2的i次方,而不一定是1
        // 比如,a = 0010011
        // a的第0位是1,第1位是1,第4位是1
        // (a & (1<<4)) == 16(不是1),说明a的第4位是1状态
        System.out.print((num & (1 << i)) == 0 ? "0" : "1");
    }
    System.out.println();
}

直接写

int c = 0b1001110

十六进制 0x4e

取反

a
~a

相反数

~a+1

注意,最小的值转不过去,因为压根不存在。比如 4 位二进制,最小的是-8,相反数是 8,但 4 位二进制最大的是 7,所以转不了。

位运算

位运算是对二进制数的各个位进行的运算,直接在数字的二进制表示上操作。位运算在底层编程、系统编程、性能优化、加密算法等领域非常常见。以下是一些基本的位运算符及其作用:

AND(与运算,&

  • 操作:对每一位进行逻辑与操作。只有两个对应位都为1时,结果位才为1。
  • 用途:常用于将数中特定的位清零,或者用于判断某位是否为1。
  • 示例0101 & 0011 = 0001

OR(或运算,|

  • 操作:对每一位进行逻辑或操作。两个对应位中只要有一个为1,结果位就为1。
  • 用途:常用于将数中特定的位设置为1。
  • 示例0101 | 0011 = 0111

XOR(异或运算,^

  • 操作:对每一位进行逻辑异或操作。当两个对应位不同时,结果位为1;相同时为0。
  • 用途:常用于切换特定位的状态,或者用于不使用临时变量交换两个数。
  • 示例0101 ^ 0011 = 0110

NOT(非运算,~

  • 操作:对数的每一位进行逻辑非操作。将1变为0,将0变为1。
  • 用途:用于对位模式进行反转。
  • 示例~0101 = 1010(在8位表示中为1111010

Left Shift(左移运算,<<

  • 操作:将二进制全部位向左移动指定的位数,右边空出的位用0填充。
  • 用途:常用于乘以2的幂。
  • 示例0101 << 2 = 010100

Right Shift(右移运算,>>

  • 操作:将二进制全部位向右移动指定的位数,左边空出的位根据数字的类型决定(有符号数通常用符号位填充,无符号数用0填充)。
  • 用途:常用于除以2的幂。
  • 示例0101 >> 2 = 0001

>>>>> 的区别

在许多编程语言中,>>>>>都是位移运算符,但它们在处理位移时的行为有所不同,特别是在处理有符号整数时。这两种运算符主要在像Java这样的语言中区别比较明显,因为JavaScript、Python等其他语言通常只使用>>来处理右移,没有>>>运算符。以下是这两个运算符的主要区别:

>> (算术右移)

  • 描述>>是算术右移运算符,它会将数字的二进制表示向右移动指定的位数。
  • 关键行为:在移位时,左侧空出的位将根据原始数字的符号位(即最左边的位)来填充。如果数字是正数,左侧填充0;如果是负数,左侧填充1。
  • 目的:这种填充方式保持了数字的符号不变,使得算术右移适合有符号数的处理。

>>> (逻辑右移)

  • 描述>>>是逻辑右移运算符,它同样将数字的二进制表示向右移动指定的位数。
  • 关键行为:不论原始数字的符号如何,移位时左侧总是用0来填充。
  • 目的:这种处理方式不保留数字的符号,逻辑右移通常用于无符号数的处理,因为它确保移位后的高位始终为0,避免产生意外的负数。

示例

考虑一个有符号整数和对这两种运算符的应用,例如在Java中:

int num = -8; // 二进制表示为 11111000(假设是8位表示)

int resultArithmetic = num >> 3; // 算术右移3位,结果为 11111111,或者 -1
int resultLogical = num >>> 3;   // 逻辑右移3位,结果为 00011111,或者 31

位运算的效果和效率很大程度上依赖于处理的数据的位数和计算机的体系结构。这些运算通常在底层编程中用于数据处理、硬件控制、实现性能优化的算法中。

二进制的加法运算

二进制是可以直接加法的,无视正负数,都通用 比如 1101 + 0110 1101 是-3,0110 是 6,结果应该是 4,用二进制表示就是 0011 列竖式算到就是0011,为什么前面各种取反,加一,减一,但这边加法能直接算,你可以理解他就是这样,这是一个规则,但我们也可以思考一下。 最高位不是符号位吗,为啥也能参与运算,但最高位真的是符号位吗?我们是可以通过最高位是 0 还是 1 判断出这个数的正负,如果他只是符号的一个标识,那我们 -3 应该是第一位表示符号是 1,后三位应该是 3,也就是 011,组合起来就是 1011,这样我们判断这个数是 -3 其实更直观。但现在 -3 是 1101,为什么? 因为最高位其实不是符号位,他表示-8,最高位为 1 就是 1*-8,所以后面三位加起来其实是 5,-8+5=-3。 所以所谓的符号位能参与进来运算也是这个原因,他代表的就是一个数。 有符号和没符号其实是一样的,没符号,4 位就是 0-15,有符号其实就是把 0 拉到最中间,就是-8 到 7,可以体会一下,我觉得符号位这个解释才是最通用的,不管解释任何正负相关的都适用。