大数乘法问题及其高效算法
2018-03-22 19:32
459 查看
题目
编写两个任意位数的大数相乘的程序,给出计算结果。比如:题目描述: 输出两个不超过100位的大整数的乘积。输入: 输入两个大整数,如1234567 和 123
输出: 输出乘积,如:151851741或者
求 1234567891011121314151617181920 * 2019181716151413121110987654321 的乘积结果
分析
所谓大数相乘(Multiplication algorithm),就是指数字比较大,相乘的结果超出了基本类型的表示范围,所以这样的数不能够直接做乘法运算。参考了很多资料,包括维基百科词条Multiplication algorithm,才知道目前大数乘法算法主要有以下几种思路:模拟小学乘法:最简单的乘法竖式手算的累加型;分治乘法:最简单的是Karatsuba乘法,一般化以后有Toom-Cook乘法;
快速傅里叶变换FFT:(为了避免精度问题,可以改用快速数论变换FNTT),时间复杂度O(N lgN lglgN)。具体可参照Schönhage–Strassen algorithm;
中国剩余定理:把每个数分解到一些互素的模上,然后每个同余方程对应乘起来就行;
Furer’s algorithm:在渐进意义上FNTT还快的算法。不过好像不太实用,本文就不作介绍了。大家可以参考维基百科Fürer’s algorithm
解法
我们分别实现一下以上算法,既然不能直接使用乘法做运算,最简单最容易想到的办法就是模拟乘法运算。1、模拟乘法手算累加
7 8 9 6 5 2 × 3 2 1 1 ----------------- 7 8 9 6 5 2 <---- 第1趟 7 8 9 6 5 2 <---- 第2趟 .......... <---- 第n趟 ----------------- ? ? ? ? ? ? ? ? <---- 最后的值用另一个数组表示如上所示,乘法运算可以分拆为两步:第一步,是将乘数与被乘数逐位相乘;
第二步,将逐位相乘得到的结果,对应相加起来。
这有点类似小学数学中,计算乘法时通常采用的“竖式运算”。用C++简单实现了这个算法,代码如下:vector<int> bigNumerMultiply(vector<int> vec1, vector<int> vec2)
{
vector<int> result;//中间求和的结果,并逆序保存
for(int i=vec2.size()-1; i>-1; --i)
{
int carry = 0;
vector<int> single;
for(int j=vec1.size()-1; j>-1; --j)
{
int r = vec2[i]*vec1[j] + carry;
int digit = r % 10;
carry = r / 10;
single.push_back(digit);
}
if(carry != 0)
{
single.push_back(carry);
}
int resultCarry = 0;
int count = 0;
int k = 0;
int m = 0;
int offset = vec2.size() - 1 - i;//加法的偏移位
vector<int> middleResult;
//vec2每位乘法的结果与上一轮的求和结果相加,从右向左做加法并进位
while(k < single.size() || m < result.size())
{
int kv = 0;
int mv = 0;
if(k < single.size() && count >= offset)
{
kv = single[k++];
}
if(m < result.size())
{
mv = result[m++];
}
int sum = resultCarry + kv + mv;
middleResult.push_back(sum % 10);
resultCarry = sum / 10;
++count;
}
if(resultCarry != 0)
{
middleResult.push_back(resultCarry);
}
result.clear();
result = middleResult;
}
//将结果逆序
int len = result.size();
for(int i = 0; i < len / 2; ++i)
{
swap(result[i],result[len-i-1]);
}
return result;
}看了以上的代码,感觉思路虽然很简单,但是实现起来却很麻烦,那么我们有没有别的方法来实现这个程序呢?答案是有的,接下来我来介
4000
绍第二种方法。
2、模拟乘法累加 - 改进
简单来说,方法二就是先不算任何的进位,也就是说,将每一位相乘,相加的结果保存到同一个位置,到最后才计算进位。例如:计算98×21,步骤如下 9 8× 2 1
-------------
(9)(8) <---- 第1趟: 98×1的每一位结果
(18)(16) <---- 第2趟: 98×2的每一位结果
-------------
(18)(25)(8) <---- 这里就是相对位的和,还没有累加进位 这里唯一要注意的便是进位问题,我们可以先不考虑进位,当所有位对应相加,产生结果之后,再考虑。从右向左依次累加,如果该位的数字大于10,那么我们用取余运算,在该位上只保留取余后的个位数,而将十位数进位(通过模运算得到)累加到高位便可,循环直到累加完毕。核心代码如下:vector<int> bigNumberMultiply2(vector<int> vec1, vector<int> vec2)
{
vector<int> result(vec1.size()+vec2.size());
for(int i = 0; i < vec1.size(); ++i)
{
for(int j = 0; j < vec2.size(); ++j)
{
result[i+j+1] += vec1[i] * vec2[j];
}
}
for(int k = result.size()-1; k>0; --k)
{
if(result[k] > 10)
{
result[k-1] += result[k] / 10;
result[k] %= 10;
}
}
/*
例如10*10,result的第一位为0,数第一位为0没有意义,所以删掉
*/
if(result[0] == 0)
{
vector<int>::iterator ite1 = result.begin();
result.erase(ite1);
}
return result;
}!!注意:这里的进位有个大坑,因为result[]数组是从左到右记录相对位的和(还没有进位),而最后的进位是从右向左累加进位,这样的话,如果最高位,也就是最左侧那一位的累加结果需要进位的话,result[]数组就没有空间存放了。
而正好
result[]数组的最后一位空置,不可能被占用,我们就响应地把num1的第i位与num2的第j位相乘,结果应该存放在结果的第i+j位上的这个结果往后顺移一位(
放到第i+j+1位),最后从右向左累加时就多了一个空间。
相关文章推荐
- 【算法】大数乘法问题及其高效算法
- 计算机算法设计与分析作业01:分治法求解大数乘法+L型骨牌的棋盘覆盖问题
- 每周算法练习——大数的乘法问题
- 大数乘法的几种算法分析及比较(2014腾讯南京笔试题)
- 网络流问题:最大流及其算法
- 深入N皇后问题的两个最高效算法的详解 分类: C/C++ 2014-11-08 17:22 117人阅读 评论(0) 收藏
- N皇后问题的两个最高效的算法
- 大数据量的存储,以及分表常见算法及其带来的问题,和微薄url的加密规则猜想
- 算法题-大数相乘问题
- 动态规划解乘法表问题——算法解题报告
- 大数乘法的几种算法分析及比较(2014腾讯南京笔试题)
- N皇后问题:基于局部搜索策略的高效算法
- 算法基础:大数求和问题
- 2-SAT问题及其算法
- Prim 算法及其高效实现
- 算法提高 P1001 (大数乘法)
- 高效质数判断算法及其JS实现
- 算法:求比指定数大且最小的“不重复数”问题的高效实现
- 1-2-5组合问题的最高效完整算法
- 2-SAT问题及其算法