位运算实现整数加法解析与实现---剑指offer-JZ65 不用加减乘除做加法

目录

问题的核心

题目代码

二进制加法的基本原理

位运算的应用 

题目步骤

为什么要循环?循环中的与运算他的进位值具体代表什么呢?他是谁的进位值呢?


在计算机科学和编程中,位运算是一种高效的运算方式,尤其是在资源受限的环境下。最近,我遇到了一个有趣的问题:如何仅使用位运算实现两个整数的加法?这个问题不仅仅是一个编程练习,更深入地理解了计算机如何在底层处理数据。

题目连接:不用加减乘除做加法_牛客题霸_牛客网 (nowcoder.com)

问题的核心

问题是这样的:不使用传统的加法运算符(+),如何计算两个整数的和?关键在于理解二进制加法的原理,以及如何用位运算(特别是异或和 与运算,以及位移操作)来模拟这一过程。

题目代码

import java.util.*;
public class Solution {
    public int Add(int num1,int num2) {
        // add表示进位值
        int add = num2;
        // sum表示总和
        int sum = num1;
        // 当不再有进位的时候终止循环
        while (add != 0) {
            int temp = sum ^ add;

            // 进位值是用与运算产生的
            add = (sum & add) << 1;

            // 更新sum为新的和
            sum = temp;
        }
        return sum;
    }
}

二进制加法的基本原理

二进制加法遵循着简单的规则:

  • 0 + 0 = 0(无进位)
  • 1 + 0 = 1 或 0 + 1 = 1(无进位)
  • 1 + 1 = 10(产生一个进位)

在二进制加法中,只有当两个位都是1时,才会产生进位。现在,让我们分析位运算符如何应用于这个规则:

  1. 异或运算(XOR):异或运算符 ^ 在二进制加法中用于计算不包括进位的和。它符合以下规则:

    • 0 ^ 0 = 0
    • 1 ^ 0 = 1
    • 0 ^ 1 = 1
    • 1 ^ 1 = 0 注意最后一条:当两个位都是1时,异或运算的结果是0,这和二进制加法中的1 + 1 = 0(不考虑进位)是一致的。因此,异或运算可以给出加法的非进位结果。
  2. 与运算(AND):与运算符 & 在二进制加法中用于计算进位。它的规则是:

    • 0 & 0 = 0
    • 1 & 0 = 0
    • 0 & 1 = 0
    • 1 & 1 = 1 注意最后一条:只有当两个位都是1时,与运算的结果是1。在二进制加法中,这是唯一产生进位的情况。因此,与运算可以确定是否有进位。
  3. 左移运算:当我们知道哪些位产生了进位后,需要将这些进位左移一位,因为在二进制中,进位总是影响下一个更高的位。比如,二进制中的 10(十进制中的2)是由低位上的进位产生的。

结合这些规则,加法操作可以这样进行:

  • 使用异或运算计算非进位的和。
  • 使用与运算和左移运算计算进位。
  • 将这两个结果相加,即重复上述两步,直到没有进位产生。

当进位为0时,异或运算的结果就是最终的加法结果。这是因为二进制加法本质上就是这样一连串的非进位和和进位的计算过程。


位运算的应用 

  • 非进位和:这是两个数进行异或(XOR)运算的结果。异或运算在每一位上模拟了加法,但不考虑进位。例如,对于二进制数来说:

    非进位和实际上是两个数相加时每一位的结果,如果那一位没有进位的话。

    • 0 XOR 0 = 0
    • 1 XOR 0 = 1
    • 0 XOR 1 = 1
    • 1 XOR 1 = 0(实际应该是2,但因为不考虑进位,所以这里是0)
  • 进位:这是两个数进行与(AND)运算然后左移一位的结果。与运算确定哪些位在相加时会产生进位:

    左移一位是因为在二进制加法中,进位总是影响下一个更高的位。

    • 0 AND 0 = 0
    • 1 AND 0 = 0
    • 0 AND 1 = 0
    • 1 AND 1 = 1(只有这种情况会产生进位)

合并非进位和和进位:在进行位运算实现加法的过程中,我们首先计算非进位和(通过异或运算)和进位(通过与运算和左移)。这两个结果必须结合起来,以得到最终的加法总和。为此,我们再次执行异或运算,这次是在非进位和与进位之间进行。这个过程可能需要重复进行,因为新的非进位和与进位之间可能还会产生新的进位。我们继续这个过程,直到没有更多的进位产生,此时的非进位和就是最终的加法结果。


题目步骤

通过这种方式,我们可以使用位运算来模拟标准的加法操作。我们已经知道:

  1. 异或运算给出了非进位的加法结果。
  2. 与运算再左移一位给出了进位的结果。

现在,我们需要将这两个结果相加。但是,因为我们不能使用传统的加法运算符,我们必须重复使用这两种位运算(异或和与运算)来实现这一点。

让我们将这个过程分解为几个步骤:

  1. 首次计算

    • sum = num1 ^ num2:这是非进位的加和。
    • add = (num1 & num2) << 1:这是进位的结果。
  2. 重复计算:现在我们有了一个新的 sum(非进位加和)和 add(进位)。但是,我们不能仅仅将它们相加,因为在 add 中可能存在新的进位。因此,我们重复相同的过程:用异或运算处理非进位加和,用与运算处理新的进位。

  3. 更新 sumadd

    • 新的 sum 是当前 sumadd 的异或运算结果。
    • 新的 add 是当前 sumadd 的与运算结果左移一位。
  4. 循环直到无进位:这个过程持续进行,直到 add 为0。当 add 为0时,表示没有更多的进位需要处理,此时 sum 包含了最终的加法结果。

每一轮循环,我们都在处理新产生的进位。最终,当所有的进位都被处理完毕(即 add 为0),我们得到最终的加和结果。这是通过位运算模拟加法过程的关键所在。

为什么要循环?循环中的与运算他的进位值具体代表什么呢?他是谁的进位值呢?

在二进制加法中,进位是一个非常重要的概念。当你将两个二进制数相加时,如果两个相应的位都是1,就会产生一个进位。这个进位不是加在当前的位上,而是加在下一个更高的位上。这就是为什么在与运算之后需要将结果左移一位,因为这样可以将进位移到正确的位置。

让我们用一个具体的例子来说明这一点:

假设我们有两个二进制数 10111101

  1. 第一步:异或运算

    • 1011 ^ 1101 = 0110(这是不考虑进位的加和)
  2. 第一步:与运算

    • 1011 & 1101 = 1001(这里只有两个位都是1时结果才是1,表示这些位置上会产生进位)
  3. 左移进位

    • 1001 左移一位得到 10010。这表示原本在第0位和第3位上的进位现在应该加到第1位和第4位上。
  4. 更新 num1 = 0110num2 = 10010

现在的关键问题是:我们有了一个不考虑进位的结果(0110),以及一个表示哪里需要进位的值(10010)。这两个结果还需要合并来得到最终的加法结果。这就是为什么需要循环的原因。在每次循环中,我们使用异或运算来合并非进位和,使用与运算和左移来计算新的进位。只有当没有更多的进位需要处理时(即进位结果为0),这个过程才会停止。

继续以上面的例子为例,进行下一轮计算:

  1. 第二步:异或运算

    • 0110 ^ 10010 = 10100(合并非进位和和进位)
  2. 第二步:与运算

    • 0110 & 10010 = 00010(进位产生)
  3. 第三部:左移进位
    • 0110 & 10010 << 1 = 100
  4. 更新 num1 = 10100num2 = 100

下面简化步骤:

第三轮计算

非进位和:10100 ^ 100 = 10000 (二进制 10000,十进制 16)

进位:(10100 & 100) << 1 = 1000 (二进制 1000,十进制 8)

更新 num1 = 10000num2 = 1000

第四轮计算

非进位和:10000 ^ 1000 = 11000 (二进制 11000,十进制 24)

进位:(10000 & 1000) << 1 = 0 (二进制 0,十进制 0)

更新 num1 = 11000num2 = 0

结束num2 变为0,循环结束。最终结果是 num1 = 11000 (二进制 11000,十进制 24)

你可能感兴趣的:(剑指offer,数据结构与算法分析,算法,数据结构,java,笔记,位运算)