给定两个表示两个整数值的二进制字符串,求两个字符串的乘积。例如,如果第一个位串是“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算法,它可以用于任何基础。
以下是上述算法的 C# 实现:
// C# implementation of Karatsuba algorithm for bit string
// multiplication.
using System;
using System.Collections.Generic;
class GFG {
// Convert bit strings to same length by adding leading
// 0s in the smaller string.
// Returns the new length.
private static int MakeEqualLength(ref string str1,
ref string str2)
{
int len1 = str1.Length;
int len2 = str2.Length;
if (len1 < len2) {
str1 = str1.PadLeft(len2 - len1 + len1, '0');
return len2;
}
else if (len1 > len2) {
str2 = str2.PadLeft(len1 - len2 + len2, '0');
}
return len1; // If len1 >= len2
}
// Adds two bit sequences and returns the addition
private static string AddBitStrings(string first,
string second)
{
string result = ""; // To store the sum bits
// make the lengths same before adding
int length = MakeEqualLength(ref first, ref second);
int carry = 0; // Initialize carry
// Add all bits one by one
for (int i = length - 1; i >= 0; i--) {
int first_bit = int.Parse(first[i].ToString());
int second_bit
= int.Parse(second[i].ToString());
// boolean expression for sum of 3 bits
int sum
= (first_bit ^ second_bit ^ carry) + '0';
result = (char)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 != 0) {
result = '1' + result;
}
return result;
}
// Multiplies single bits of strings a and b
private static int MultiplySingleBit(char a, char b)
{
return int.Parse(a.ToString())
* int.Parse(b.ToString());
}
// Multiplies two bit strings X and Y and returns result
// as long integer
private 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 = X.PadLeft(n, '0');
Y = Y.PadLeft(n, '0');
// Base cases
if (n == 0)
return 0;
if (n == 1)
return MultiplySingleBit(X[0], Y[0]);
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(AddBitStrings(Xl, Xr),
AddBitStrings(Yl, Yr));
// Combine the three products to get the final
// result.
return P1 * (1L << (2 * sh))
+ (P3 - P1 - P2) * (1L << sh) + P2;
}
// Test the implementation
public static void Main(string[] args)
{
Console.WriteLine(Multiply("1100", "1010"));
Console.WriteLine(Multiply("110", "1010"));
Console.WriteLine(Multiply("11", "1010"));
Console.WriteLine(Multiply("1", "1010"));
Console.WriteLine(Multiply("0", "1010"));
Console.WriteLine(Multiply("111", "111"));
Console.WriteLine(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
using System;
using System.Text;
// Class for classical binary multiplication
class BinaryMultiplier
{
public string MakeMultiplication(string str1, string str2)
{
string allSum = "";
// Iterate through each bit of the second binary string
for (int j = 0; j < str2.Length; j++)
{
int secondDigit = int.Parse(str2[j].ToString());
if (secondDigit == 1)
{
// Shift the first binary string and add it to the result
string shifted = MakeShifting(str1, str2.Length - (j + 1));
allSum = AddBinary(shifted, allSum);
}
}
return allSum;
}
// Function to add binary strings with carry
public string AddBinary(string a, string b)
{
string result = "";
int s = 0;
int i = a.Length - 1;
int j = b.Length - 1;
while (i >= 0 || j >= 0 || s == 1)
{
s += (i >= 0) ? int.Parse(a[i].ToString()) : 0;
s += (j >= 0) ? int.Parse(b[j].ToString()) : 0;
result = (s % 2).ToString() + result;
s /= 2;
i--;
j--;
}
return result;
}
// Function to shift a binary string to the left by a given number of steps
public string MakeShifting(string str, int stepnum)
{
string shifted = str;
for (int i = 0; i < stepnum; i++)
{
shifted += '0';
}
return shifted;
}
// Function to convert binary string to decimal number
public void BinaryStringToDecimal(string result)
{
Console.WriteLine("Binary Result: " + result);
int val = 0;
for (int i = result.Length - 1; i >= 0; i--)
{
if (result[i] == '1')
{
val += (int)Math.Pow(2, (result.Length - 1) - i);
}
}
Console.WriteLine("Decimal Result (Not proper for Large Binary Numbers):" + val);
}
}
// Class for Karatsuba binary multiplication
class Karatsuba
{
// Function to control the lengths of binary strings and make their lengths equal
public int LengthController(ref string str1, ref string str2)
{
int len1 = str1.Length;
int len2 = str2.Length;
if (len1 < len2)
{
str1 = new string('0', len2 - len1) + str1;
return len2;
}
else if (len1 > len2)
{
str2 = new string('0', len1 - len2) + str2;
}
return len1;
}
// Function to add binary strings with carry
public string AddStrings(string first, string second)
{
string result = "";
int length = LengthController(ref first, ref second);
int carry = 0;
for (int i = length - 1; i >= 0; i--)
{
int firstBit = int.Parse(first[i].ToString());
int secondBit = int.Parse(second[i].ToString());
int sum = (firstBit ^ secondBit ^ carry);
result = sum.ToString() + result;
carry = (firstBit & secondBit) | (secondBit & carry) | (firstBit & carry);
}
if (carry != 0)
{
result = '1' + result;
}
return result;
}
// Function to convert decimal number to binary string
public string DecimalToBinary(int number)
{
string result = "";
if (number <= 0)
{
return "0";
}
else
{
while (number > 0)
{
int num = number % 2;
result = num.ToString() + result;
number /= 2;
}
return result;
}
}
// Function to perform binary string subtraction with overflow
public string Subtraction(string lhs, string rhs)
{
int length = LengthController(ref lhs, ref rhs);
int diff;
StringBuilder resultBuilder = new StringBuilder();
for (int i = length - 1; i >= 0; i--)
{
int lhsDigit, rhsDigit;
if (!int.TryParse(lhs[i].ToString(), out lhsDigit) || !int.TryParse(rhs[i].ToString(), out rhsDigit))
{
// Handle parsing error, e.g., throw an exception or set a default value
throw new FormatException("Error parsing integer from string.");
}
diff = lhsDigit - rhsDigit;
if (diff >= 0)
{
resultBuilder.Insert(0, DecimalToBinary(diff));
}
else
{
for (int j = i - 1; j >= 0; j--)
{
int currentValue = lhs[j] - '0';
if (currentValue > 0)
{
lhs = lhs.Remove(j, 1).Insert(j, (currentValue - 1).ToString());
break;
}
}
resultBuilder.Insert(0, DecimalToBinary(diff + 2));
}
}
return resultBuilder.ToString();
}
// Function to shift a binary string to the left by a given number of steps
public string MakeShifting(string str, int stepnum)
{
string shifted = str;
for (int i = 0; i < stepnum; i++)
{
shifted += '0';
}
return shifted;
}
// Function that is the core of the Karatsuba algorithm
public string Multiply(string X, string Y)
{
int n = LengthController(ref X, ref Y);
if (n == 1)
return ((int.Parse(Y[0].ToString()) == 1) && (int.Parse(X[0].ToString()) == 1)) ? "1" : "0";
int fh = n / 2;
int sh = n - fh;
string Xl = X.Substring(0, fh);
string Xr = X.Substring(fh, sh);
string Yl = Y.Substring(0, fh);
string Yr = Y.Substring(fh, sh);
Karatsuba karatsuba = new Karatsuba();
string P1 = karatsuba.Multiply(Xl, Yl);
string P2 = karatsuba.Multiply(Xr, Yr);
string P3 = karatsuba.Multiply(karatsuba.AddStrings(Xl, Xr), karatsuba.AddStrings(Yl, Yr));
return karatsuba.AddStrings(
karatsuba.AddStrings(karatsuba.MakeShifting(P1, 2 * (n - n / 2)), P2),
karatsuba.MakeShifting(karatsuba.Subtraction(P3, karatsuba.AddStrings(P1, P2)), n - (n / 2))
);
}
}
class Program
{
static void Main()
{
/*Console.WriteLine("Please give the First Binary number : ");
string firstNumber = Console.ReadLine();
Console.WriteLine("Please give the Second Binary number : ");
string secondNumber = Console.ReadLine();
*/
string firstNumber = "011011010100";
string secondNumber = "10111010111";
int len1 = firstNumber.Length;
int len2 = secondNumber.Length;
Karatsuba karatsubaObj = new Karatsuba();
if (len1 < len2)
{
firstNumber = new string('0', len2 - len1) + firstNumber;
}
else if (len1 > len2)
{
secondNumber = new string('0', len1 - len2) + secondNumber;
}
Console.WriteLine("\nClassical Algorithm:");
BinaryMultiplier binaryMultiplier = new BinaryMultiplier();
string classic = binaryMultiplier.MakeMultiplication(firstNumber, secondNumber);
binaryMultiplier.BinaryStringToDecimal(classic);
Console.WriteLine("\nKaratsuba Algorithm:");
string karatsuba = karatsubaObj.Multiply(firstNumber, secondNumber);
binaryMultiplier.BinaryStringToDecimal(karatsuba);
}
}
时间复杂度:
二进制字符串乘法的经典方法和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 类的“乘法”方法是针对三个乘积中的每一个递归调用的。