1. 模拟竖式运算+滑动窗口
2. 分治法 Karatsuba 乘法原理(大数乘法)
1. 模拟竖式运算+滑动窗口
如 134 * 25
__134
x__25
按竖式运算应该是这样分组
4 — 5 --------> 20 ,进位2,此位0
34 — 25 --------> 3x5+2x4 = 23 进位2,此位3,+上一个进位2 ,最终为5
13 — 25 -------->1x5+2x3 = 11 进位1,此位1+2=3
1 — 2 --------> 2+1=3
所以134*25 = 3350
其中34和25 应该 前者从左到右,后者从右到左,依次相乘,最终求和。
即3x5, 2x4,最后求和结果为15+8=23,那么这一位的结果就是3,进位为2.
此外,可以发现发现一共分了 n1+n2-1 组,因此一个运行n1+n2-1 次乘法。n1,n2分别表示两个运算数的位数
#include <cstring>
#include <cmath>
using namespace std;
int ch2int(char ch){
return ch-'0';
}
int main(){
string num1;
string num2;
while(cin>>num1>>num2){
if(num1=="0" || num2=="0"){
cout<<"0"<<endl;
continue;
}
string res;
int val=0;
int next_val=0;#下一位进位
int curr_val=0;#当前进位
int n1 = num1.length();
int n2 = num2.length();
int raiseFlag = true;//表示滑动窗口长度是否继续上升或者维持不变或者减少的标志
// 默认n2更短,也就是小数字放在下面区做乘法
if(n1<n2){//否则就交换
string temp = num1;
num1=num2;
num2=temp;
n1 = num1.length();
n2 = num2.length();
}
int maxWinSize = n2;//最大滑动窗口长度要取最短的那个
int index1=n1-1,index2=n2-1;//分别表示数字1和2的下标
int winSize = 1;//滑动窗口初始为1
for(int i=0;i<n1+n2-1;i++){//模拟竖式运算,所以一共会运行n1+n2-1次
val = 0;
curr_val = next_val;//curr_val表示就是上次的进位放到这次的运算来用了,在运算完成后加上上一次的进位
next_val = 0;
for(int j=0;j<winSize;j++){
//每次都是取滑动窗口,num1的最左边开始,num2的最右边开始,依次相乘,结果相加进val里
//示例:4567*132 7--2 67--32 567--132 456--132 45-- 13 4--1
val += ch2int(num1[index1+j])*ch2int(num2[index2-j]);
// cout<<"part:"<<j+1<<":"<<num1[index1+j]<<"x"<<num2[index2-j]<<'\t';
}
// cout<<endl;
val += curr_val;//加上上一次进位的结果
next_val = val/10;//取这次的进位给下一次
val%=10;//取个位数,作为这个位置的最终结果
res+=val+'0';//加入最终结果中
if(winSize==maxWinSize){//查看标志
raiseFlag=false;//修改标志,不再上升,而是维持不变或者下降
}
if(raiseFlag){//窗口上升期
winSize++;
index1--;//index1一直往左走,直到为0
}
else{//维持不变或者下降
if(index1==0){//下降期
winSize--;
index2--;//index2终于开始下降
}
else{//维持不变,仅仅是index1继续往左走
index1--;
}
}
}
if(next_val!=0){//最后如果还有进位也要记得加上去
res+=next_val+'0';
}
for(int i=res.length()-1;i>=0;i--){//反向输出整个结果string
cout<<res[i];
}
cout<<endl;
}
return 0;
}
2. 分治法 Karatsuba 乘法原理(大数乘法)
h(x) = f(x) * g(x)
其中
f(x) = a * x + b,
g(x) = c * x + d.
x是一个基准值,一般是最大的操作数十进制位数的一半,最后对十进制数直接左移操作(也就是后面补0)即可
A,C分别为原计算值的高位和低位,分割直接就是对半切
B,D同理
然后小的乘法也继续递归调用,由此叫分治。
比如
1234 x 567
X位数取最大位数的一半,4/2=2
(12x100 + 34 ) x (56x100 + 7)
其中,12*X,也就是12*100可以直接用字符串12后面加两个0来实现,所以我们可以先不管X
原式展开=acX^2 + (ad+bc)X + bd
而根据一个巧合:引入一个新的式子, (a + b) * (c + d)
将其展开为 ac+ad+bc+bd
所以原式中间项的 (ad+bc) ------> (a+b) * (c+d) - (ac+bc)
而对于计算机来说,ac和bd的值刚好也要算,因此一次函数调用的乘运算为3次,勉强小于O(n^2)的时间复杂度.
acX^2 + [ (a+b) * (c+d) - (ac+bc) ]X + bd
所以一次运算要求的东西为:
ac,bd,(a+b)*(c+d)
然后就是加法和减法了.
精简版本:
#include <iostream>
#include <string>
#define max(a,b) ((a) > (b) ? (a) : (b))
using namespace std;
//大数相加
string add(string lhs, string rhs) {
int length = max(lhs.size(), rhs.size());
int carry = 0;//进位
int sum_col; // sum of two digits in the same column
string result;
//让短的补0对齐
// pad the shorter string with zeros
while (lhs.size() < length)
lhs.insert(0,"0");
while (rhs.size() < length)
rhs.insert(0,"0");
//逆序遍历
// build result string from right to left
for (int i = length-1; i >= 0; i--) {
sum_col = (lhs[i]-'0') + (rhs[i]-'0') + carry;
carry = sum_col/10;
result.insert(0,to_string(sum_col % 10));
}
if (carry)
result.insert(0,to_string(carry));
// remove leading zeros
return result.erase(0, min(result.find_first_not_of('0'), result.size()-1));
}
//大数相除
string subtract(string lhs, string rhs) {
int length = max(lhs.size(), rhs.size());
int diff;
string result;
while (lhs.size() < length)
lhs.insert(0,"0");
while (rhs.size() < length)
rhs.insert(0,"0");
for (int i = length-1; i >= 0; i--) {
diff = (lhs[i]-'0') - (rhs[i]-'0');
if (diff >= 0)
result.insert(0, to_string(diff));
else {
// borrow from the previous column
int j = i - 1;
while (j >= 0) {
lhs[j] = ((lhs[j]-'0') - 1) % 10 + '0';
if (lhs[j] != '9')
break;
else
j--;
}
result.insert(0, to_string(diff+10));
}
}
return result.erase(0, min(result.find_first_not_of('0'), result.size()-1));
}
string multiply(string lhs, string rhs) {
int length = max(lhs.size(), rhs.size());
while (lhs.size() < length)
lhs.insert(0,"0");
while (rhs.size() < length)
rhs.insert(0,"0");
if (length == 1)
return to_string((lhs[0]-'0')*(rhs[0]-'0'));
string lhs0 = lhs.substr(0,length/2);
string lhs1 = lhs.substr(length/2,length-length/2);
string rhs0 = rhs.substr(0,length/2);
string rhs1 = rhs.substr(length/2,length-length/2);
string p0 = multiply(lhs0,rhs0);
string p1 = multiply(lhs1,rhs1);
string p2 = multiply(add(lhs0,lhs1),add(rhs0,rhs1));
string p3 = subtract(p2,add(p0,p1));
//右移B个单位,B是基值
for (int i = 0; i < 2*(length-length/2); i++)//po是AC.AC*X^2,所以右移两倍的B单位
p0.append("0");
for (int i = 0; i < length-length/2; i++)//p3是中间项,只有一个x,所以右移一个B单位
p3.append("0");
string result = add(add(p0,p1),p3);
return result.erase(0, min(result.find_first_not_of('0'), result.size()-1));
}
int main() {
string s1, s2;
cin >> s1 >> s2;
cout << multiply(s1,s2) << endl;
return 0;
}
下面是自己复现的版本,很多冗余操作,仅作个人笔记
#include <bits\stdc++.h>
using namespace std;
string strAdd(string lhs,string rhs){
if(lhs=="0"){
return rhs;
}
if(rhs=="0"){
return lhs;
}
int l1 = lhs.length();
int l2 = rhs.length();
int MimLen = min(lhs.length(),rhs.length());
int MaxLen = max(lhs.length(),rhs.length());
int tmp=0;
int carry=0;
//开数组是因为我们要逆序存储,而string一直insert(0,ch)非常耗时
char* res = new char[MaxLen+2];//预留了最左进位和\0两个的位置
int index = MaxLen+1;
res[index--] = '\0';
int i;
for(i=0;i<MimLen;i++){//按最小长度算
tmp = carry + (lhs[l1-1-i] - '0') + (rhs[l2-1-i] - '0');
carry = tmp/10;
res[index--] = tmp%10+'0';
}
//处理剩下的,两个循环只可能进去一个
while(i<l1){
res[index--] = (lhs[l1-1-i] - '0') + carry +'0';
carry = 0;//只有第一次array才可能不为0
i++;
}
while(i<l2){
res[index--] = (rhs[l2-1-i] - '0') + carry +'0';
carry = 0;//只有第一次array才可能不为0
i++;
}
//如果是两数的位数相等,则只需要处理最左进位
if(carry){//如果不是位数相等的情况,则会在上面的while中array置为0,不会进入.
res[index--] = carry + '0';
}
string result(res+1-carry);//如果位数相等时有进位要处理,则起始值为res,否则为res+1(因为开数组时预留了最左进位和\0两个的位置)
delete[] res;
return result;
}
string strSubtract(string lhs_,string rhs_){
if(lhs_=="0" && rhs_!="0"){
return "-"+rhs_;
}
if(rhs_=="0"){
return lhs_;
}
int l1 = lhs_.length();
int l2 = rhs_.length();
//如果最终差为正数则直接求,如果差为负数,则反过来求,最后再加负号
int minusFlag = 0;
string lhs = lhs_;
string rhs = rhs_;
if(l1<l2 || (l1==l2 && lhs_[0]-'0' < rhs_[0] -'0')){
minusFlag = 1;
lhs = rhs_;
rhs = lhs_;
l1 = lhs.length();
l2 = rhs.length();
}
// while(rhs.length()<l1){
// rhs.insert(0,"0");
// }
char* res = new char[l1+1];
int index = l1;
res[index--] = '\0';
int carry=0;
int borrow=0;
int i;
//从后往前,将被减数每一位都变成二位数,也就是默认都借位了.
for(i=0;i<l2;i++){
int tmp = carry + 10 + (lhs[l1-1-i] - '0') - borrow - (rhs[l2-1-i] - '0');
carry = tmp/10;
res[index--] = tmp%10 + '0';
borrow = 1;//第一次借位为0,后续借位都是1.
}
if(i<l1){
res[index--] = ( carry + 10 + (lhs[l1-1-i] - '0') - borrow ) % 10 + '0';
i++;
}
while(i<l1){
res[index--] = lhs[l1-1-i];
i++;
}
int resStart = 0;
while(res[resStart]=='0')
resStart++;
string result(res+resStart);
delete[] res;
if(result.length()==0)
result = "0";
if(minusFlag)
return "-"+result;
return result;
}
string strMultiply(string lhs,string rhs){
// cout<<endl<<"New"<<lhs<<" * "<<rhs<<endl<<endl;
int len = max(lhs.length(),rhs.length());
while(lhs.length()<len){
lhs.insert(0,"0");
}
while(rhs.length()<len){
rhs.insert(0,"0");
}
if(len==1){
return to_string((lhs[0]-'0') * (rhs[0] - '0'));
}
string A = lhs.substr(0,len/2);
string B = lhs.substr(len/2,len-len/2);
string C = rhs.substr(0,len/2);
string D = rhs.substr(len/2,len-len/2);
// printf("A:%s B:%s C:%s D:%s\n",A.c_str(),B.c_str(),C.c_str(),D.c_str());
string p1 = strMultiply(A,C);
string p2 = strMultiply(B,D);
string p3 = strMultiply(strAdd(A,B),strAdd(C,D));
string p4 = strSubtract(p3,strAdd(p1,p2));
// printf("AC:%s BD:%s p3:%s p4:%s\n",p1.c_str(),p2.c_str(),p3.c_str(),p4.c_str());
for(int i=0;i<2*(len - len/2);i++){
p1.append("0");
}
for(int i=0;i<(len - len/2);i++){
p4.append("0");
}
// printf("%s %s\n",p1.c_str(),p2.c_str());
string res = strAdd(p1,strAdd(p2,p4));
return res.erase(0,min(res.find_first_not_of('0'),res.length()-1));
}
int main(){
string lhs;
string rhs;
while(cin>>lhs>>rhs){
if(lhs=="0" || rhs=="0"){
cout<<"0"<<endl;
continue;
}
string res = strMultiply(lhs,rhs);
cout<<res<<endl;
}
return 0;
}