使用分而治之算法进行快速乘法的 Karatsuba 算法

发布于 2024-10-28 22:22:24 字数 5508 浏览 5 评论 0

给定两个表示两个整数值的二进制字符串,找到两个字符串的乘积。例如,如果第一位字符串是 1100 ,第二位字符串是 1010 ,则输出应为 120。

为简单起见,让两个字符串的长度相同且为 n。

天真的方法是遵循我们在学校学习的过程。逐个取第二个数字的所有位,然后将其与第一个数字的所有位相乘。最后添加所有乘法。该算法花费 O(n ^ 2) 时间。

产品

使用Divide 和 Conquer ,我们可以将两个整数相乘,从而降低了时间复杂度。我们将给定的数字分为两半。让给定的数字为 X 和 Y。

为简单起见,我们假设 n 为偶数

X =  Xl*2n/2 + Xr    [Xl and Xr contain leftmost and rightmost n/2 bits of X]
Y =  Yl*2n/2 + Yr    [Yl and Yr contain leftmost and rightmost n/2 bits of Y]

乘积 XY 可以写成如下形式。

XY = (Xl*2n/2 + Xr)(Yl*2n/2 + Yr)
   = 2n XlYl + 2n/2(XlYr + XrYl) + XrYr

如果我们看上面的公式,有四个大小为 n / 2 的乘法,所以我们基本上将大小为 n / 2 的问题分为四个大小为 n / 2 的子问题。但这无济于事,因为递归 T(n)= 4T(n / 2)+ O(n) 的解是 O(n ^ 2)。该算法的棘手部分是将中间的两个项更改为其他形式,以便仅执行一次额外的乘法就足够了。以下是中间两个术语的棘手表达式。

XlYr + XrYl = (Xl + Xr)(Yl + Yr) - XlYl- XrYr

因此 XY 的最终值变为

XY = 2n XlYl + 2n/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 = 22ceil(n/2) XlYl + 2ceil(n/2) * [(Xl + Xr)(Yl + Yr) - XlYl - XrYr] + XrYr

上面的算法称为 Karatsuba 算法,它可以用于任何基础。

以下是上述算法的 C++实现。

// C++ implementation of Karatsuba algorithm for bit string multiplication.
#include
#include
  
using namespace std;
  
// FOLLOWING TWO FUNCTIONS ARE COPIED FROM http://goo.gl/q0OhZ
// 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
int makeEqualLength(string &str1, string &str2)
{
    int len1 = str1.size();
    int len2 = str2.size();
    if (len1 < len2)
    {
        for (int i = 0 ; i < len2 - len1 ; i++)
            str1 = '0' + str1;
        return len2;
    }
    else if (len1 > len2)
    {
        for (int 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
string addBitStrings( string first, string second )
{
    string result;  // To store the sum bits
  
    // make the lengths same before adding
    int length = makeEqualLength(first, second);
    int carry = 0;  // Initialize carry
  
    // Add all bits one by one
    for (int i = length-1 ; i >= 0 ; i--)
    {
        int firstBit = first.at(i) - '0';
        int secondBit = second.at(i) - '0';
  
        // boolean expression for sum of 3 bits
        int sum = (firstBit ^ secondBit ^ carry)+'0';
  
        result = (char)sum + result;
  
        // boolean expression for 3-bit addition
        carry = (firstBit&secondBit) | (secondBit&carry) | (firstBit&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
int multiplyiSingleBit(string a, string b)
{  return (a[0] - '0')*(b[0] - '0');  }
  
// The main function that multiplies two bit strings X and Y and returns
// result as long integer
long int 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 = makeEqualLength(X, Y);
  
    // Base cases
    if (n == 0) return 0;
    if (n == 1) return multiplyiSingleBit(X, Y);
  
    int fh = n/2;   // First half of string, floor(n/2)
    int sh = (n-fh); // Second half of string, ceil(n/2)
  
    // Find the first half and second half of first string.
    // Refer http://goo.gl/lLmgn for substr method
    string Xl = X.substr(0, fh);
    string Xr = X.substr(fh, sh);
  
    // Find the first half and second half of second string
    string Yl = Y.substr(0, fh);
    string Yr = Y.substr(fh, sh);
  
    // Recursively calculate the three products of inputs of size n/2
    long int P1 = multiply(Xl, Yl);
    long int P2 = multiply(Xr, Yr);
    long int P3 = multiply(addBitStrings(Xl, Xr), addBitStrings(Yl, Yr));
  
    // Combine the three products to get the final result.
    return P1*(1<<(2*sh)) + (P3 - P1 - P2)*(1<<sh) +="" p2;="" }=""   ="" driver="" program="" to="" test="" above="" functions="" int="" main()="" {=""     printf="" ("%ld\n",="" multiply("1100",="" "1010"));="" multiply("110",="" multiply("11",="" multiply("1",="" multiply("0",="" multiply("111",="" "111"));="" "11"));="" <="" code=""></sh)>

输出:

120
60
30
10
0
49
9

时间复杂度:上述解决方案的时间复杂度为 O(n log 2 3 )= O(n 1.59 )。

使用另一种分而治之算法,快速傅里叶变换,可以进一步提高乘法的时间复杂度。我们很快将在单独的文章中讨论快速傅立叶变换。

锻炼

上面的程序返回一个 long int 值,不适用于大字符串。扩展上述程序以返回字符串而不是 long int 值。

解决方案

大量的乘法过程是计算机科学中的一个重要问题。给定的方法使用分而治之方法。运行代码以查看常规二进制乘法和 Karatsuba 算法的时间复杂度比较。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

入怼

暂无简介

0 文章
0 评论
22 人气
更多

推荐作者

玍銹的英雄夢

文章 0 评论 0

我不会写诗

文章 0 评论 0

十六岁半

文章 0 评论 0

浸婚纱

文章 0 评论 0

qq_kJ6XkX

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文