给定两个表示两个整数值的二进制字符串,求两个字符串的乘积。例如,如果第一个位串是“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算法,它可以用于任何基础。
以下是上述算法的 JavaScript 实现:
// JavaScript implementation of Karatsuba algorithm for bit string multiplication.
// Helper method: given two unequal sized bit strings, converts them to
// same length by adding leading 0s in the smaller string. Returns the
// the new length
function make_equal_length(str1, str2) {
let len1 = str1.length;
let len2 = str2.length;
if (len1 < len2) {
for (let i = 0; i < len2 - len1; i++) {
str1 = '0' + str1;
}
return len2;
} else if (len1 > len2) {
for (let i = 0; i < len1 - len2; i++) {
str2 = '0' + str2;
}
}
return len1; // If len1 >= len2
}
// The main function that adds two bit sequences and returns the addition
function add_bit_strings(first, second) {
let result = ""; // To store the sum bits
// make the lengths same before adding
let length = make_equal_length(first, second);
let carry = 0; // Initialize carry
// Add all bits one by one
for (let i = length - 1; i >= 0; i--) {
let first_bit = parseInt(first[i]);
let second_bit = parseInt(second[i]);
// boolean expression for sum of 3 bits
let sum = (first_bit ^ second_bit ^ carry) + '0'.charCodeAt(0);
result = String.fromCharCode(sum) + result;
// boolean expression for 3-bit addition
carry = (first_bit & second_bit) | (second_bit & carry) | (first_bit & carry);
}
// if overflow, then add a leading 1
if (carry) {
result = '1' + result;
}
return result;
}
// A utility function to multiply single bits of strings a and b
function multiply_single_bit(a, b) {
return parseInt(a[0]) * parseInt(b[0]);
}
// The main function that multiplies two bit strings X and Y and returns
// result as long integer
function multiply(X, Y) {
// Find the maximum of lengths of x and Y and make length
// of smaller string same as that of larger string
let n = Math.max(X.length, Y.length);
X = X.padStart(n, '0');
Y = Y.padStart(n, '0');
// Base cases
if (n == 0) return 0;
if (n == 1) return parseInt(X[0]) * parseInt(Y[0]);
let fh = Math.floor(n / 2); // First half of string
let sh = n - fh; // Second half of string
// Find the first half and second half of first string.
let Xl = X.slice(0, fh);
let Xr = X.slice(fh);
// Find the first half and second half of second string
let Yl = Y.slice(0, fh);
let Yr = Y.slice(fh);
// Recursively calculate the three products of inputs of size n/2
let P1 = multiply(Xl, Yl);
let P2 = multiply(Xr, Yr);
let P3 = multiply((parseInt(Xl, 2) + parseInt(Xr, 2)).toString(2), (parseInt(Yl, 2) + parseInt(Yr, 2)).toString(2));
// Combine the three products to get the final result.
return P1 * (1 << (2 * sh)) + (P3 - P1 - P2) * (1 << sh) + P2
}
console.log(multiply("1100", "1010"))
console.log(multiply("110", "1010"))
console.log(multiply("11", "1010"))
console.log(multiply("1", "1010"))
console.log(multiply("0", "1010"))
console.log(multiply("111", "111"))
console.log(multiply("11", "11"))
输出
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
class BinaryMultiplier {
makeMultiplication(str1, str2) {
let allSum = "";
for (let j = 0; j < str2.length; j++) {
const secondDigit = parseInt(str2[j]);
if (secondDigit === 1) {
const shifted = this.makeShifting(str1, str2.length - (j + 1));
allSum = this.addBinary(shifted, allSum);
}
}
return allSum;
}
addBinary(a, b) {
let result = "";
let s = 0;
let i = a.length - 1;
let j = b.length - 1;
while (i >= 0 || j >= 0 || s === 1) {
s += (i >= 0) ? parseInt(a[i]) : 0;
s += (j >= 0) ? parseInt(b[j]) : 0;
result = String(s % 2) + result;
s = Math.floor(s / 2);
i--;
j--;
}
return result;
}
makeShifting(str, stepnum) {
let shifted = str;
for (let i = 0; i < stepnum; i++) {
shifted += '0';
}
return shifted;
}
binaryStringToDecimal(result) {
console.log("Binary Result : " + result);
let val = 0;
for (let i = result.length - 1; i >= 0; i--) {
if (result[i] === '1') {
val += Math.pow(2, (result.length - 1) - i);
}
}
console.log("Decimal Result (Not proper for Large Binary Numbers):" + val);
}
}
class Karatsuba {
lengthController(str1, str2) {
const len1 = str1.length;
const len2 = str2.length;
if (len1 < len2) {
for (let i = 0; i < len2 - len1; i++) {
str1 = '0' + str1;
}
return len2;
} else if (len1 > len2) {
for (let i = 0; i < len1 - len2; i++) {
str2 = '0' + str2;
}
}
return len1;
}
addStrings(first, second) {
let result = "";
const length = this.lengthController(first, second);
let carry = 0;
for (let i = length - 1; i >= 0; i--) {
const firstBit = parseInt(first[i]);
const secondBit = parseInt(second[i]);
const sum = (firstBit ^ secondBit ^ carry).toString();
result = sum + result;
carry = (firstBit & secondBit) | (secondBit & carry) | (firstBit & carry);
}
if (carry) {
result = '1' + result;
}
return result;
}
decimalToBinary(number) {
let result = "";
if (number <= 0) {
return "0";
} else {
let i = 0;
while (number > 0) {
const num = number % 2;
result = num + result;
number = Math.floor(number / 2);
i++;
}
return result;
}
}
subtraction(lhs, rhs) {
const length = this.lengthController(lhs, rhs);
let diff;
let result = "";
for (let i = length - 1; i >= 0; i--) {
diff = parseInt(lhs[i]) - parseInt(rhs[i]);
if (diff >= 0) {
result = this.decimalToBinary(diff) + result;
} else {
for (let j = i - 1; j >= 0; j--) {
lhs[j] = (parseInt(lhs[j]) - 1) % 10 + '0';
if (lhs[j] !== '1') {
break;
}
}
result = this.decimalToBinary(diff + 2) + result;
}
}
return result;
}
makeShifting(str, stepnum) {
let shifted = str;
for (let i = 0; i < stepnum; i++) {
shifted += '0';
}
return shifted;
}
multiply(X, Y) {
const n = this.lengthController(X, Y);
if (n === 1) return ((parseInt(Y[0]) === 1) && (parseInt(X[0]) === 1)) ? "1" : "0";
const fh = Math.floor(n / 2);
const sh = n - fh;
const Xl = X.substr(0, fh);
const Xr = X.substr(fh, sh);
const Yl = Y.substr(0, fh);
const Yr = Y.substr(fh, sh);
const P1 = this.multiply(Xl, Yl);
const P2 = this.multiply(Xr, Yr);
const P3 = this.multiply(this.addStrings(Xl, Xr), this.addStrings(Yl, Yr));
return this.addStrings(this.addStrings(this.makeShifting(P1, 2 * (n - n / 2)), P2),
this.makeShifting(this.subtraction(P3, this.addStrings(P1, P2)), n - (n / 2)));
}
}
// main function
function main() {
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("Please give the First Binary number : ", function (firstNumber) {
rl.question("\nPlease give the Second Binary number : ", function (secondNumber) {
rl.close();
let len1 = firstNumber.length;
let len2 = secondNumber.length;
if (len1 < len2) {
firstNumber = '0'.repeat(len2 - len1) + firstNumber;
} else if (len1 > len2) {
secondNumber = '0'.repeat(len1 - len2) + secondNumber;
}
console.log("\nClassical Algorithm : ");
const classicalTime = new Date().getTime();
const binaryMultiplier = new BinaryMultiplier();
const classic = binaryMultiplier.makeMultiplication(firstNumber, secondNumber);
console.log((new Date().getTime() - classicalTime) / 1000.0 + "\n");
binaryMultiplier.binaryStringToDecimal(classic);
console.log("\nKaratsuba Algorithm : ");
const karatsubaTime = new Date().getTime();
const karatsubaObj = new Karatsuba();
const karatsuba = karatsubaObj.multiply(firstNumber, secondNumber);
console.log((new Date().getTime() - karatsubaTime) / 1000.0 + "\n");
binaryMultiplier.binaryStringToDecimal(karatsuba);
});
});
}
// call main function
main();
时间复杂度:
二进制字符串乘法的经典方法和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 类的“乘法”方法是针对三个乘积中的每一个递归调用的。