分治——大整数的乘法
话不多说进入正题:
两个整数 NUM1*NUM2,由于整数范围的限制,数值太大的话在NUM1 NUM2或者结果的表示时会产生溢出,因此我们用字符串来进行运算:
基本处理:
- 用字符串存储NUM1 NUM2
- 所有数字字符串都在第一位带上符号’+’ / ‘-’ (为了处理的方便)
算法思想:
1.把原问题划分为规模大致相同的子问题:
- 取n为字符串NUM1,NUM2两者中较短的长度值,
- NUM1前L1-n/2为A,后n/2为B
NUM2前L2-n/2为C,后n/2为D
-
NUM1xNUNM2= (A *10(n/2)+B) ( C *10(n/2)+D)= AC *10n+(AD+BC) *10(n/2)+BD=AC *10n+((A-B)(D-C) + AC +BD) *10(n/2) +BD
- 最终分解为规模较小的相同类型子问题:AC BD (A-B)(D-C) 三次乘法运算
2.合并子问题的解得出原问题的解
- ① AC *10n ② BD ③ (A-B)(C-D)*10n三个结果相加
3.基本子算法:
- 当两个乘数的规模在10^3数量级的时候,直接用整数相乘得到结果
- 协助算法:求两个字符串数的和、差
代码:
//求两个包含符号在内的字符串数的乘积(两数的数量级大致相同)
string LargeIntegerMulti( string s1, string s2 ){
char sign='+';
if(s1[0]!=s2[0])
sign='+';
int l1=s1.length(), l2=s2.length();
if(l1<=3||l2<=3){
stringstream ss;
ss<<s1<<" "<<s2;
int num1,num2;
ss>>num1>>num2;
int result=num1*num2;
ss.clear();
ss<<result;
string sresult;
ss>>sresult;
if(result>0)
sresult="+"+sresult;
return sresult;
}
int n=l1>l2?(l2-1):(l1-1);
n=n/2;
string A(s1,1,l1-n-1), B(s1,l1-n,n),C(s2,1,l2-n-1),D(s2,l2-n,n);
string A_B=Plus("+"+A,"-"+B),
D_C=Plus("+"+D,"-"+C);
string AC=LargeIntegerMulti("+"+A,"+"+C),
BD=LargeIntegerMulti("+"+B,"+"+D),
A_BD_C=LargeIntegerMulti(A_B,D_C);
A_BD_C=Plus(Plus(AC,BD),A_BD_C);
AC+=string(2*n,'0');
A_BD_C+=string(n,'0');
string result=Plus(Plus(AC,BD),A_BD_C);
if(result[0]!=sign) result[0]='-';
return result;
}
- 协助算法
//求两个带符号的字符串数的和
char temp[100];
string Plus(string s1,string s2){
char sign1=s1[0],sign2=s2[0],rsign,c1,c2,c;
string str1(s1.rbegin(),s1.rend()),str2(s2.rbegin(),s2.rend());
int index=0,l1=s1.length(),l2=s2.length(),carry=0;
//减法
if(sign1!=sign2){
if(Greater(string(s1,1,l1-1),string(s2,1,l2-1))){
rsign=(sign1=='+'?'+':'-');
}
else{
rsign=(sign2=='+'?'+':'-');
swap(str1,str2);
}
while(index<str1.length()-1){
c1=str1[index];
if(index<str2.length()-1)
c2=str2[index];
else
c2='0';
c=c1-c2-carry+'0';
if(c<'0'){
c+=10;
carry=1;
}
else
carry=0;
temp[index++]=c;
}
}
else{//加法
rsign=(sign1=='+'?'+':'-');
if(!Greater(string(s1,1,l1-1),string(s2,1,l2-1)))
swap(str1,str2);
while(index<str1.length()-1){
c1=str1[index];
if(index<str2.length()-1)
c2=str2[index];
else
c2='0';
c=c1+c2+carry-'0';
if(c>'9'){
c-=10;carry=1;
}
else
carry=0;
temp[index++]=c;
}
if(carry!=0)
temp[index++]='1';
}
string s(temp,temp+index);
return rsign+string(s.rbegin(),s.rend()) ;
}
- 测试(主函数)
int main()
{
string s1,s2;
cin>>s1>>s2;
if(s1[0]!='-')s1="+"+s1;
if(s2[0]!='-')s2="+"+s2;
stringstream ss;
string result=LargeIntegerMulti(s1,s2);
if(result[0]=='+')
result.erase(0,1);
cout<<result;
return 0;
}
Note
1.在字符串和整数的转换中巧妙地用到了字符串流,但是要注意多次使用的时候,要在使用前clear()
2.注意在每次运算里面字符串都带有符号,每次运算的结果也不要丢了正数的正号
3.字符串的加法运算不能自作聪明地转化为整数进行运算,因为当乘法运算的结果比较大的时候,整数会溢出,必须包装字符串加减法的函数。
4.这个算法在两个乘数都比较大的时候会更有效率,当只有一个乘数较大的时候,建议包装一个类似于字符串数加法的函数,从低位到高位进行运算