给定两个表示两个整数值的二进制字符串,求两个字符串的乘积。例如,如果第一个位串是“1100”,第二个位串是“1010”,则输出应为 120。
为了简单起见,让两个字符串的长度相同并为n。
一种天真的方法是遵循我们在学校学习的过程。逐一取出第二个数字的所有位并将其与第一个数字的所有位相乘。最后将所有乘法相加。该算法需要 O(n^2) 时间。
使用Divide and Conquer,我们可以以较低的时间复杂度将两个整数相乘。我们将给定的数字分成两半。设给定数字为 X 和 Y。
为简单起见,我们假设 n 是偶数
X = Xl*2 n/2 + Xr [Xl 和 Xr 包含 X 的最左边和最右边的 n/2 位]
Y = Yl*2 n/2 + Yr [Yl 和 Yr 包含 Y 的最左边和最右边的 n/2 位]
乘积 XY 可以写如下。
XY = (Xl*2 n/2 + Xr)(Yl*2 n/2 + Yr)
= 2 n XlYl + 2 n/2 (XlYr + XrYl) + XrYr
如果我们看一下上面的公式,有四次大小为n/2的乘法,所以我们基本上将大小为n的问题分为四个大小为n/2的子问题。但这并没有帮助,因为递归 T(n) = 4T(n/2) + O(n) 的解是 O(n^2)。该算法的棘手部分是将中间两项更改为其他形式,这样只需一次额外的乘法就足够了。以下是中间两项的棘手表达。
XlYr + XrYl = (Xl + Xr)(Yl + Yr) - XlYl- XrYr
所以 XY 的最终值变为
XY = 2 n XlYl + 2 n/2 * [(Xl + Xr)(Yl + Yr) - XlYl - XrYr] + XrYr
通过上述技巧,递推式变为 T(n) = 3T(n/2) + O(n) 并且该递推式的解为 O(n 1.59 )。
如果输入字符串的长度不同且不均匀怎么办?为了处理不同长度的情况,我们在开头附加 0。为了处理奇数长度,我们将floor(n/2)位放在左半部分,将ceil(n/2)位放在右半部分。因此 XY 的表达式变为以下形式。
XY = 2 2ceil(n/2) XlYl + 2 ceil(n/2) * [(Xl + Xr)(Yl + Yr) - XlYl - XrYr] + XrYr
上述算法称为Karatsuba算法,它可以用于任何基础。
以下是上述算法的 java 实现:
// Java implementation of Karatsuba algorithm for bit string multiplication.
public class GFG {
// Driver Code
public static void main(String[] args)
{
System.out.println(multiply("1100", "1010"));
System.out.println(multiply("110", "1010"));
System.out.println(multiply("11", "1010"));
System.out.println(multiply("1", "1010"));
System.out.println(multiply("0", "1010"));
System.out.println(multiply("111", "111"));
System.out.println(multiply("11", "11"));
}
// Helper method: given two unequal sized bit strings,
// converts them to same length by adding leading 0s in
// the smaller string. Returns the new length
private static int makeEqualLength(StringBuilder str1,
StringBuilder str2)
{
int len1 = str1.length();
int len2 = str2.length();
if (len1 < len2) {
for (int i = 0; i < len2 - len1; i++) {
str1.insert(0, '0');
}
return len2;
}
else if (len1 > len2) {
for (int i = 0; i < len1 - len2; i++) {
str2.insert(0, '0');
}
}
return len1; // If len1 >= len2
}
// The main function that adds two bit sequences and
// returns the addition
private static StringBuilder
addBitStrings(StringBuilder first, StringBuilder second)
{
StringBuilder result = new StringBuilder();
int length = makeEqualLength(first, second);
int carry = 0;
// Add all bits one by one
for (int i = length - 1; i >= 0; i--) {
int firstBit = first.charAt(i) - '0';
int secondBit = second.charAt(i) - '0';
// boolean expression for sum of 3 bits
int sum = (firstBit ^ secondBit ^ carry) + '0';
result.insert(0, (char)sum);
// boolean expression for 3-bit addition
carry = (firstBit & secondBit)
| (secondBit & carry)
| (firstBit & carry);
}
// if overflow, then add a leading 1
if (carry == 1) {
result.insert(0, '1');
}
return result;
}
// A utility function to multiply single bits of strings
// a and b
private static int multiplySingleBit(int a, int b)
{
return a * b;
}
// The main function that multiplies two bit strings X
// and Y and returns result as long integer
public static long multiply(String X, String Y)
{
// Find the maximum of lengths of X and Y and make
// length of smaller string same as that of larger
// string
int n = Math.max(X.length(), Y.length());
X = String.format("%" + n + "s", X)
.replace(' ', '0');
Y = String.format("%" + n + "s", Y)
.replace(' ', '0');
// Base cases
if (n == 0)
return 0;
if (n == 1)
return Integer.parseInt(X)
* Integer.parseInt(Y);
int fh = n / 2; // First half of string
int sh = n - fh; // Second half of string
// Find the first half and second half of first
// string.
String Xl = X.substring(0, fh);
String Xr = X.substring(fh);
// Find the first half and second half of second
// string
String Yl = Y.substring(0, fh);
String Yr = Y.substring(fh);
// Recursively calculate the three products of
// inputs of size n/2
long P1 = multiply(Xl, Yl);
long P2 = multiply(Xr, Yr);
long P3 = multiply(Integer.toBinaryString(
Integer.parseInt(Xl, 2)
+ Integer.parseInt(Xr, 2)),
Integer.toBinaryString(
Integer.parseInt(Yl, 2)
+ Integer.parseInt(Yr, 2)));
// Combine the three products to get the final
// result.
return P1 * (1L << (2 * sh))
+ (P3 - P1 - P2) * (1L << sh) + P2;
}
}
输出
120
60
30
10
0
49
9
时间复杂度:上述解决方案的时间复杂度为 O(n log 2 3 ) = O(n 1.59 )。
使用另一种分而治之算法,即快速傅里叶变换,可以进一步提高乘法的时间复杂度。我们很快将作为单独的帖子讨论快速傅立叶变换。
辅助空间: O(n)
练习:
上面的程序返回一个 long int 值,不适用于大字符串。扩展上面的程序以返回字符串而不是 long int 值。
解决方案:
大数的乘法过程是计算机科学中的一个重要问题。给定方法使用分而治之的方法。
运行代码来查看普通二元乘法和 Karatsuba 算法的时间复杂度比较。 您可以在此存储库
中查看完整代码
例子:
第一个二进制输入:101001010101010010101001010100101010010101010010101第二个二进制输入:101001010101010010101001010100101010010101010010101十进制输出:无可呈现的输出:2.1148846e+30
第一个二进制输入:1011第二个二进制输入:1000十进制输出:88输出:5e-05
import java.math.BigInteger;
public class BinaryMultiplication {
// Method to add binary strings
public static String addBinary(String a, String b) {
BigInteger aBigInt = new BigInteger(a, 2);
BigInteger bBigInt = new BigInteger(b, 2);
BigInteger sum = aBigInt.add(bBigInt);
return sum.toString(2);
}
// Method to shift binary string
public static String shiftLeft(String str, int n) {
return str + "0".repeat(n);
}
// Classical binary multiplication
public static String classicalMultiply(String str1, String str2) {
String result = "0";
int n = str2.length();
for (int i = 0; i < n; i++) {
if (str2.charAt(n - 1 - i) == '1') {
result = addBinary(result, shiftLeft(str1, i));
}
}
return result;
}
// Karatsuba multiplication
public static String karatsubaMultiply(String X, String Y) {
int n = Math.max(X.length(), Y.length());
// Make the lengths equal
X = String.format("%" + n + "s", X).replace(' ', '0');
Y = String.format("%" + n + "s", Y).replace(' ', '0');
if (n == 1) {
return Integer.toString(Integer.parseInt(X) * Integer.parseInt(Y));
}
int m = n / 2;
String Xl = X.substring(0, m);
String Xr = X.substring(m);
String Yl = Y.substring(0, m);
String Yr = Y.substring(m);
String P1 = karatsubaMultiply(Xl, Yl);
String P2 = karatsubaMultiply(Xr, Yr);
String P3 = karatsubaMultiply(addBinary(Xl, Xr), addBinary(Yl, Yr));
String C1 = shiftLeft(P1, 2 * (n - m));
String C2 = shiftLeft(addBinary(subtractBinary(P3, addBinary(P1, P2)), P2), n - m);
return addBinary(addBinary(C1, C2), P2);
}
// Subtract binary strings
public static String subtractBinary(String a, String b) {
BigInteger aBigInt = new BigInteger(a, 2);
BigInteger bBigInt = new BigInteger(b, 2);
BigInteger difference = aBigInt.subtract(bBigInt);
return difference.toString(2);
}
public static void main(String[] args) {
String firstNumber = "011011010100";
String secondNumber = "10111010111";
System.out.println("Classical Algorithm:");
String classicResult = classicalMultiply(firstNumber, secondNumber);
System.out.println("Binary Result: " + classicResult);
System.out.println("Decimal Result: " + new BigInteger(classicResult, 2).toString(10));
System.out.println("Karatsuba Algorithm:");
String karatsubaResult = karatsubaMultiply(firstNumber, secondNumber);
System.out.println("Binary Result: " + karatsubaResult);
System.out.println("Decimal Result: " + new BigInteger(karatsubaResult, 2).toString(10));
}
}
时间复杂度:
二进制字符串乘法的经典方法和Karatsuba方法的时间复杂度都是O(n^2)。
在经典方法中,时间复杂度为O(n^2),因为循环迭代了 n 次。 addBinary() 方法的时间复杂度是恒定的,因为循环最多运行两次迭代。
在 Karatsuba 方法中,时间复杂度为O(n^2),因为对三个产品中的每一个都会递归调用 Karasuba 类的“乘法”方法。 addStrings() 方法的时间复杂度是恒定的,因为循环最多运行两次迭代。
辅助空间:
二进制字符串乘法的经典方法和Karatsuba方法的辅助空间都是O(n)。
在经典方法中,辅助空间是O(n),因为循环迭代了 n 次并且使用单个字符串来存储结果。addBinary() 方法的空间复杂度是恒定的,因为循环最多运行两次迭代。
在Karatsuba 方法中,辅助空间为O(n),因为Karatsuba 类的“乘法”方法是针对三个乘积中的每一个递归调用的。