引言
很久很久以前,有人是这样算乘法的:x*y,好经典~
有人是这样算复数乘法的:(a+bi)(c+di)=ac-bd+(bc+ad)i,实数乘法进行了四次,这种普通乘法时间复杂度是O(n^2)。
但是!我们伟大的数学家高斯(Gauss)发现,其实上式可以通过三个实数乘法来完成:ac, bd ,(a+b)(c+d)。
这种算法可以应用到大数相乘之中,当数小的时候这只是很小的改进,但是当数很大的时候,若递归地应用这个小幅改进,则算法的效率得到极其显著的提升!!分治算法介绍
给定两个n位的二进制整数x和y(当然次算法不限定二进制数),作为x乘y的第一步,我们将x和y一分为二,每个数的左半部分和右半部分都是n/2位的二进制整数:
例如,如果X=10110110B(B表示该数是二进制数),那么XL=1011B, XR=0110B, 同时X=1011B*2^4+0110B. x和y的乘积从而可以写为以下形式:
看上去我们要进行4次实数乘法(乘2的幂相当于左移运算不考虑),实际上,类似上面引言的介绍,只需要进行三次乘法:XLYL,XRYR,(XL + XR)(YL + YR), 因为XLYR + XRYL = (XL + XR)(YL + YR) - XLYL - XRYR
运行时间改进为O(n^1.59)。伪代码
下面本人写的代码主要也是参照这个伪代码实现,不同的是加了个符号表示数的正负。函数实现细节(输入x和y,还有它们的位数n,二者等长)
1.计算x*y的正负
2.定义变量
3.判断x和y是否为0,若是0返回0
4.若x和y位数均为1,则直接返回x*y的结果,不必做分治
5.计算x和y的各个部分,XL, XR, YL, YR, 注意考虑n为奇数的情况,则分L比R长一位。
6.计算P1=XLYL,P2=XRYR,P3=(XL + XR)(YL + YR)
7.返回P1x10^(2*floor(n/2))+(P3-P1-P2)x10^(n/2)+P2(此处对应伪代码的最后一行,本人用了十进制)完整代码
#include<iostream>
#include<cmath>
using namespace std;
#define SIGN(A) ((A>0)?1:-1)//定义一个符号函数表示正负
int multiply(int x, int y,int n)
{
int sign = SIGN(x)*SIGN(y);
int XR, XL, YR, YL;
int P1, P2, P3;
if (x == 0 || y == 0)
return 0;
if (n == 1)
return x*y;
else
{
//计算X和y的各个部分
XL = x / pow(10, n / 2);
XR = x - XL*(pow(10, n / 2));
YL = y / pow(10, n / 2);
YR = y - YL*(pow(10, n / 2));
//分治计算
P1 = multiply(XL, YL, n-n/2);
P2 = multiply(XR, YR, n / 2);
P3 = multiply(XL + XR, YL + YR, n-n/2);
return sign*(P1*pow(10, 2*floor(n/2)) + (P3 - P1 - P2)*pow(10,n/2) + P2);
}
}
int main()
{
printf("请输入两个等长的数字x和y...\n");
int x, y, n;
printf("请输入x:\n");
cin >> x;
printf("请输入y:\n");
cin >> y;
printf("请输入x和y的位数:\n");
cin >> n;
printf("---------------------------------------------\n");
printf("x * y = ");
cout<<multiply(x, y, n)<<endl;
printf("非分治算法求得 x * y = ");
cout << x*y<<endl;
system("pause");
return 0;
}
- 结果截图