大数乘法
来源:互联网 发布:帝国cms 火车头采集 编辑:程序博客网 时间:2024/06/10 19:33
大数乘法是一个经常会遇到的问题,在Java中这个问题很容易解决,直接用BigInteger就okl了。但是在C++、C中就不那么容易了,在做LeetCode过程中就遇到了这道题,第43题“Multiply Strings”:https://leetcode.com/problems/multiply-strings/#/description。 我在网上查了一下,也看了LeetCode上的discussion,大致总结三种方法:普通进位法,分治法,傅里叶变换法(FFT)。
一 :普通进位法
很多人先把string反转过来,再做模拟乘法的操作,因为string中第一位对应数值的最高位,当然不倒转也可以。 这个方法很好理解,代码量也很少。时间复杂度是O(n^2),我在discussion上看到大神写的代码十分漂亮,就记下来了,以后求职笔试过程中遇到以便能默写出来。
string Mutiply(string num1, string num2){ string sum(num1.length() + num2.length() + 1, '0'); int carry; //进位 int tmp; for(int i=num1.length()-1; i>=0; i--){ for(int j=num2.length()-1; j>=0; j--){ tmp = sum[i+j+1]-'0' + (num1[i]-'0') * (num2[j]-'0') + carry; sum[i+j+1] = tmp % 10; carry = tmp / 10; }//for sum[i] = carry; }//for size_t pos = sum.find_first_not_of("0"); if(string::npos != pos) return sum.substr(pos); else return "0";}
二: 分治法
http://cnn237111.blog.51cto.com/2359144/1201901
#include <iostream> #include <sstream> #include <string> using namespace std; //string类型转换成int类型int string_to_num(string k)//string字符串变整数型例如str="1234",转换为整数的1234. { int back; stringstream instr(k); instr>>back; return back; } //整形数转换为string类型string num_to_string(int intValue){ string result; stringstream stream; stream << intValue;//将int输入流 stream >> result;//从stream中抽取前面放入的int值 return result;}//在字符串str前添加s个零string stringBeforeZero(string str,int s){ for(int i=0;i<s;i++) str.insert(0,"0"); return str;}//两个大整数字符串相加,超出计算机表示范围的数也能实现相加(本函数可以实现大整数加法运算)string stringAddstring(string str1,string str2){ //假定str1和str2是相等的长度,不相等时在前面自动补零,使两个字符串长度相等 if (str1.size() > str2.size()) str2 = stringBeforeZero(str2,str1.size() - str2.size()); else if (str1.size() < str2.size()) str1 = stringBeforeZero(str1,str2.size() - str1.size()); string result; int flag=0;//前一进位是否有标志,0代表无进位,1代表有进位 for(int i=str1.size()-1;i>=0;i--) { int c = (str1[i] - '0') + (str2[i] - '0') + flag;//利用ASCII码对字符进行运算,这里加上flag代表的是:当前一位有进位时加1,无进位时加0 flag = c/10;//c大于10时,flag置为1,否则为0 c %= 10;//c大于10时取模,否则为其本身 result.insert(0,num_to_string(c));//在result字符串最前端插入新生成的单个字符 } if (0 != flag) //最后一为(最高位)判断,如果有进位则再添一位 result.insert(0,num_to_string(flag)); return result;}/*两个大整数字符串相减,超出计算机表示范围的数也能实现相减(在这里比较特殊,第一个参数一定大于第二个参数,因为:a1*b0+a0*b1=(a1+a0)*(b1+b0)-(a1*b1+a0*b0) > 0 ,所以(a1+a0)*(b1+b0) > (a1*b1+a0*b0)这个函数的两个参数,第一个代表的其实就是(a1+a0)*(b1+b0),第二个代表的其实就是(a1*b1+a0*b0)所以本函数里不用考虑最终得到结果为负数的情况,至于计算有关大整数负数相乘的问题可以通过其他途径判断*/string stringSubtractstring(string str1,string str2){ //对传进来的两个数进行修剪,如果前面几位有0则先去掉,便于统一处理 while ('0' == str1[0]&&str1.size()>1) str1=str1.substr(1,str1.size()-1); while ('0' == str2[0]&&str2.size()>1) str2=str2.substr(1,str2.size()-1); //使两个字符串长度相等 if (str1.size() > str2.size()) str2 = stringBeforeZero(str2,str1.size() - str2.size()); string result; for(int i=str1.size()-1;i>=0;i--) { int c = (str1[i] - '0') - (str2[i] - '0');//利用ASCII码进行各位减法运算 if (c < 0) //当不够减时向前一位借位,前一位也不够位时再向前一位借位,依次如下 { c +=10; int prePos = i-1; char preChar = str1[prePos]; while ('0' == preChar) { str1[prePos]='9'; prePos -= 1; preChar = str1[prePos]; } str1[prePos]-=1; } result.insert(0,num_to_string(c));//在result字符串最前端插入新生成的单个字符 } return result;}//在字符串str后跟随s个零string stringFollowZero(string str,int s){ for(int i=0;i<s;i++) str.insert(str.size(),"0"); return str;}//分治法大整数乘法实现函数string IntMult(string x,string y)//递归函数{ //对传进来的第一个数进行修剪,如果前面几位有0则先去掉,便于统一处理 while ('0' == x[0]&&x.size()>1) x=x.substr(1,x.size()-1); //对传进来的第二个数进行修剪,如果前面几位有0则先去掉,便于统一处理 while ('0' == y[0]&&y.size()>1) y=y.substr(1,y.size()-1); /*这里的f变量代表在两个数据字符串长度不想等或者不是2的指数倍的情况下,所要统一的长度,这样做是为了让数据长度为2的n次方的情况下便于利用分治法处理*/ int f=4; /*当两字符串中有任意一个字符串长度大于2时都要通过与上面定义的f值进行比较,使其达到数据长度为2的n次方,实现方式是在前面补0,这样可以保证数据值大小不变*/ if (x.size()>2 || y.size()>2) { if (x.size() >= y.size()) //第一个数长度大于等于第二个数长度的情况 { while (x.size()>f) //判断f值 f*=2; if (x.size() != f) { x = stringBeforeZero(x,f-x.size()); y = stringBeforeZero(y,f-y.size()); } }else//第二个数长度大于第一个数长度的情况 { while (y.size()>f) //判断f值 f*=2; if (y.size() != f) { x = stringBeforeZero(x,f-x.size()); y = stringBeforeZero(y,f-y.size()); } } } if (1 == x.size()) //数据长度为1时,在前面补一个0(这里之所以会出现长度为1的数据是因为前面对数据修剪过) x=stringBeforeZero(x,1); if (1 == y.size()) //数据长度为1时,在前面补一个0(这里之所以会出现长度为1的数据是因为前面对数据修剪过) y=stringBeforeZero(y,1); if (x.size() > y.size()) //最后一次对数据校正,确保两个数据长度统一 y = stringBeforeZero(y,x.size()-y.size()); if (x.size() < y.size()) //最后一次对数据校正,确保两个数据长度统一 x = stringBeforeZero(x,y.size()-x.size()); int s = x.size(); string a1,a0,b1,b0; if( s > 1) { a1 = x.substr(0,s/2); a0 = x.substr(s/2,s-1); b1 = y.substr(0,s/2); b0 = y.substr(s/2,s-1); } string result; if( s == 2) //长度为2时代表着递归的结束条件 { int na = string_to_num(a1); int nb = string_to_num(a0); int nc = string_to_num(b1); int nd = string_to_num(b0); result = num_to_string((na*10+nb) * (nc*10+nd)); } else{ //长度不为2时利用分治法进行递归运算 /*************************************************** 小提示: c = a*b = c2*(10^n) + c1*(10^(n/2)) + c0; a = a1a0 = a1*(10^(n/2)) + a0; b = b1b0 = b1*(10^(n/2)) + b0; c2 = a1*b1; c0 = a0*b0; c1 = (a1 + a0)*(b1 + b0)-(c2 + c0); ****************************************************/ string c2 = IntMult(a1,b1);// (a1 * b1) string c0 = IntMult(a0,b0);// (a0 * b0) string c1_1 = stringAddstring(a1,a0);// (a1 + a0) string c1_2 = stringAddstring(b1,b0);// (b1 + b0) string c1_3 = IntMult(c1_1,c1_2);// (a1 + a0)*(b1 + b0) string c1_4 = stringAddstring(c2,c0);// (c2 + c0) string c1=stringSubtractstring(c1_3,c1_4);// (a1 + a0)*(b1 + b0)-(c2 + c0) string s1=stringFollowZero(c1,s/2);// c1*(10^(n/2)) string s2=stringFollowZero(c2,s);// c2*(10^n) result = stringAddstring(stringAddstring(s2,s1),c0);// c2*(10^n) + c1*(10^(n/2)) + c0 } return result; } void main() { int f=1; while (1 == f) { string A,B,C,D; string num1,num2; string r; cout<<"大整数乘法运算(分治法实现)"<<endl; cout<<"请输入第一个大整数(任意长度):"; cin>>num1; int i=0; //判断第一个输入的数据是否合法,当字符串中包含非数字字符时提示用户重新输入 for(i=0 ;i < num1.size();i++) { if (num1[i]<'0' || num1[i]>'9') { cout<<"您输入的数据不合法,请输入有效的整数!"<<endl; cin>>num1; i=-1; } } cout<<"请输入第二个大整数(任意长度):"; cin>>num2; //判断第二个输入的数据是否合法,当字符串中包含非数字字符时提示用户重新输入 for(i=0 ;i < num2.size();i++) { if (num2[i]<'0' || num2[i]>'9') { cout<<"您输入的数据不合法,请输入有效的整数!"<<endl; cin>>num2 i=-1; } } r=IntMult(num1,num2); //对求得的结果进行修剪,去掉最前面的几个0 while ('0' == r[0]&&r.size()>1) { r=r.substr(1,r.size()-1); } cout<<"两数相乘结果为:"<<endl; cout<<num1<<" "<<"*"<<" "<<num2<<" "<<"="<<" "<<r<<endl<<endl; }}
三: FFT算法
#include <iostream> #include <cmath> #include <complex> #include <cstring> using namespace std; const double PI = acos(-1);typedef complex<double> cp; typedef long long int64; const int N = 1 << 16; int64 a[N], b[N], c[N << 1]; void bit_reverse_copy(cp a[], int n, cp b[]) { int i, j, k, u, m; for (u = 1, m = 0; u < n; u <<= 1, ++m); for (i = 0; i < n; ++i) { j = i; k = 0; for (u = 0; u < m; ++u, j >>= 1) k = (k << 1) | (j & 1); b[k] = a[i]; } } void FFT(cp _x[], int n, bool flag) { static cp x[N << 1]; bit_reverse_copy(_x, n, x); int i, j, k, kk, p, m; for (i = 1, m = 0; i < n; i <<= 1, ++m); double alpha = 2 * PI; if (flag) alpha = -alpha; for (i = 0, k = 2; i < m; ++i, k <<= 1) { cp wn = cp(cos(alpha / k), sin(alpha / k)); for (j = 0; j < n; j += k) { cp w = 1, u, t; kk = k >> 1; for (p = 0; p < kk; ++p) { t = w * x[j + p + kk]; u = x[j + p]; x[j + p] = u + t; x[j + p + kk] = u - t; w *= wn; } } } memcpy(_x, x, sizeof(cp) * n); } void polynomial_multiply(int64 a[], int na, int64 b[], int nb, int64 c[], int &nc) { int i, n; i = max(na, nb) << 1; for (n = 1; n < i; n <<= 1); static cp x[N << 1], y[N << 1]; for (i = 0; i < na; ++i) x[i] = a[i]; for (; i < n; ++i) x[i] = 0; FFT(x, n, 0); for (i = 0; i < nb; ++i) y[i] = b[i]; for (; i < n; ++i) y[i] = 0; FFT(y, n, 0); for (i = 0; i < n; ++i) x[i] *= y[i]; FFT(x, n, 1); for (i = 0; i < n; ++i) { c[i] =(int64)(x[i].real() / n + 0.5); } for (nc = na + nb - 1; nc > 1 && !c[nc - 1]; --nc); } const int LEN = 5, MOD = 100000; void convert(char *s, int64 a[], int &n) { int len = strlen(s), i, j, k; for (n = 0, i = len - LEN; i >= 0; i -= LEN) { for (j = k = 0; j < LEN; ++j) k = k * 10 + (s[i + j] - '0'); a[n++] = k; } i += LEN; if (i) { for (j = k = 0; j < i; ++j) k = k * 10 + (s[j] - '0'); a[n++] = k; } } void print(int64 a[], int n) { printf("%I64d", a[--n]); while (n) printf("%05I64d", a[--n]); puts(""); } char buf[N + 10]; int main() { int na, nb, nc; while (scanf("%s", buf) != EOF) { bool sign = false; if (buf[0] == '-') { sign = !sign; convert(buf + 1, a, na); } else convert(buf, a, na); scanf("%s", buf); if (buf[0] == '-') { sign = !sign; convert(buf + 1, b, nb); } else convert(buf, b, nb); polynomial_multiply(a, na, b, nb, c, nc); int64 t1, t2; t1 = 0; for (int i = 0; i < nc; ++i) { t2 = t1 + c[i]; c[i] = t2 % MOD; t1 = t2 / MOD; } for (; t1; t1 /= MOD) c[nc++] = t1 % MOD; if (sign) putchar('-'); print(c, nc); } return 0; }
阅读全文
0 0
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- 大数乘法
- Android学习之简单使用SeekBar
- 关于MySQL 通用查询日志和慢查询日志分析
- 浅谈自己对构造函数、析构函数、复制构造函数的理解
- java判断字符串是否为空的方法
- [翻译自MOS文章]警告:在rhel7 or OL(RHCK)7上安装GI 12.2.0.1时 root.sh fails并有报错"CLSRSC-400"
- 大数乘法
- readonly与const的区别
- 半同步复制
- 【时间管理】柳比歇夫之奇特的一生
- javaweb开发环境配置完全手册
- Android ContentProvider(内容提供者 )+ContentResolver(内容访问者)
- 小仙女-jquery 、ajax、jsp基础
- day1 JavaScript Drum Kit 中文指南
- 响应式网页