https://blog.csdn.net/u010983881/article/details/77503519
这个Karatsuba算法,时间复杂度仅有 O(nlog23)O(nlog23) 。下面,我就来介绍一下这个算法。
Karatsuba于1960年发明在 O(nlog23)O(nlog23) 步骤内将两个n位数相乘的Karatsuba算法。它反证了安德雷·柯尔莫哥洛夫于1956年认为这个乘法需要 Ω(n2)Ω(n2) 步骤的猜想。
首先来看看这个算法是怎么进行计算的,见下图:
我们假设要相乘的两个数是x * y。我们可以把x,y写成:
x=a∗10n/2+bx=a∗10n/2+b
y=c∗10n/2+dy=c∗10n/2+d
这里的n是数字的位数。如果是偶数,则a和b都是n/2位的。如果n是奇数,则你可以让a是n/2+1位,b是n/2位。(例如a = 12,b = 34;a = 123,b = 45),那么x*y就变成了:
x∗y=(a∗10n/2+b)∗(c∗10n/2+d)x∗y=(a∗10n/2+b)∗(c∗10n/2+d)
进一步计算,
x∗y=a∗c∗10n+(a∗d+b∗c)∗10n/2+bdx∗y=a∗c∗10n+(a∗d+b∗c)∗10n/2+bd
对比之前的计算过程。结果已经呼之欲出了。这里唯一需要注意的两点就是:
图中显示了计算5678 * 1234的过程,首先是拆分成abcd四个部分,然后分别计算ac, bd, (a+b)*(c+d),最后再用第三个算式的结果减去前面两个(其实得到的就是bc+ad,但是减少了乘法步骤),然后,计算式1后面加4个0,计算式2后面不加,计算式3后面加2个0,再把这三者相加,就是正确结果。
接下来,就来证明一下这个算法的正确性。这是一幅来自Karatsuba Multiplication Algorithm – Python Code的图,我们来看看:
我们举例来尝试一下这种算法,比如计算12345 * 6789,我们让a = 12,b = 345。同时c = 6,d = 789。也就是:
12345=12⋅1000+3456789=6⋅1000+78912345=12·1000+3456789=6·1000+789
那么a*c,b*d的结果如下:
z2=a∗c=12×6=72z0=b∗d=345×789=272205z1=(12+345)×(6+789)−z2−z0=283815−72−272205=11538z2=a∗c=12×6=72z0=b∗d=345×789=272205z1=(12+345)×(6+789)−z2−z0=283815−72−272205=11538
最终结果就是:
result=z2⋅102∗3+z1⋅103+z0result=72⋅106+11538⋅103+272205=83810205.result=z2·102∗3+z1·103+z0result=72·106+11538·103+272205=83810205.
以上就是使用分治的方式计算乘法的原理。上面这个算法,由 Anatolii Alexeevitch Karatsuba 于1960年提出并于1962年发表,所以也被称为 Karatsuba 乘法。
根据上面的思路,实现的Karatsuba乘法代码如下:
/**
* Karatsuba乘法
*/
public static long karatsuba(long num1, long num2){
//递归终止条件
if(num1 < 10 || num2 < 10) return num1 * num2;
// 计算拆分长度
int size1 = String.valueOf(num1).length();
int size2 = String.valueOf(num2).length();
int halfN = Math.max(size1, size2) / 2;
/* 拆分为a, b, c, d */
long a = Long.valueOf(String.valueOf(num1).substring(0, size1 - halfN));
long b = Long.valueOf(String.valueOf(num1).substring(size1 - halfN));
long c = Long.valueOf(String.valueOf(num2).substring(0, size2 - halfN));
long d = Long.valueOf(String.valueOf(num2).substring(size2 - halfN));
// 计算z2, z0, z1, 此处的乘法使用递归
long z2 = karatsuba(a, c);
long z0 = karatsuba(b, d);
long z1 = karatsuba((a + b), (c + d)) - z0 - z2;
return (long)(z2 * Math.pow(10, (2*halfN)) + z1 * Math.pow(10, halfN) + z0);
}
总结:
Karatsuba 算法是比较简单的递归乘法,把输入拆分成 2 部分,不过对于更大的数,可以把输入拆分成 3 部分甚至 4 部分。拆分为 3 部分时,可以使用下面的Toom-Cook 3-way 乘法,复杂度降低到 O(n^1.465)。拆分为 4 部分时,使用Toom-Cook 4-way 乘法,复杂度进一步下降到 O(n^1.404)。对于更大的数字,可以拆成 100 段,使用快速傅里叶变换FFT,复杂度接近线性,大约是 O(n^1.149)。可以看出,分割越大,时间复杂度就越低,但是所要计算的中间项以及合并最终结果的过程就会越复杂,开销会增加,因此分割点上升,对于公钥加密,暂时用不到太大的整数,所以使用 Karatsuba 就合适了,不用再去弄更复杂的递归乘法。
public class LeetcodeTest {
public static void main(String[] args) {
// String a = "1234567891011121314151617181920";
// String b = "2019181716151413121110987654321";
// String a = "999999999999";
// String b = "999999999999";
// String a = "24566";
// String b = "452053";
String a = "98";
String b = "21";
char[] charArr1 = a.trim().toCharArray();
char[] charArr2 = b.trim().toCharArray();
// 字符数组转换为int[]数组
int[] arr1 = new int[charArr1.length];
int[] arr2 = new int[charArr2.length];
for(int i = 0; i < charArr1.length; i++){
arr1[i] = charArr1[i] - '0';
}
for(int i = 0; i < charArr2.length; i++){
arr2[i] = charArr2[i] - '0';
}
// 开始计算
int[] result = LeetcodeTest.bigNumberMultiply2(arr1, arr2);
System.out.println(a + " * " + b + " = " + Arrays.toString(result).replace(", ", ""));
}
}
1234567891011121314151617181920 * 2019181716151413121110987654321 = [02492816912877266687794240983772975935013386905490061131076320]
999999999999 * 999999999999 = [999999999998000000000001]
24566 * 452053 = [11105133998]
98 * 21 = [2058]