(大数)大整数乘法问题 C++ 示例代码

转载: 【算法】大数乘法问题及其高效算法

题目

编写两个任意位数的大数相乘的程序,给出计算结果。比如:

题目描述: 输出两个不超过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

解法

我们分别实现一下以上算法,既然不能直接使用乘法做运算,最简单最容易想到的办法就是模拟乘法运算。

所谓大数相乘(Multiplication algorithm),就是指数字比较大,相乘的结果超出了基本类型的表示范围,所以这样的数不能够直接做乘法运算。
我们分别实现一下以上算法,既然不能直接使用乘法做运算,最简单最容易想到的办法就是模拟乘法运算。

1、模拟小学乘法手算

      7 8 9 6 5 2
×         3 2 1 1
-----------------
      7 8 9 6 5 2   <----17 8 9 6 5 2     <----2..........       <---- 第n趟 
-----------------
  ? ? ? ? ? ? ? ?   <---- 最后的值用另一个数组表示 

如上所示,乘法运算可以分拆为两步:

  • 第一步,是将乘数与被乘数逐位相乘;
  • 第二步,将逐位相乘得到的结果,对应相加起来。

这有点类似小学数学中,计算乘法时通常采用的“竖式运算”。
用C++简单实现了这个算法,代码如下

#include<iostream>
using namespace std;

string bAdd(string num1, string num2) {//大数加法 
	int len1 = num1.size();
	int len2 = num2.size();

	if (len1 > len2) {
		num2.insert(num2.begin(), len1 - len2, '0');
	}
	else if (len1 < len2) {
		num1.insert(num1.begin(), len2 - len1, '0');
	}//使两个操作数长度相等 

	string res = "";
	int cbit = 0;//进位
	int rmd;//余数 
	for (int i = num1.size() - 1; i >= 0; i--) {
		int sum = (num1[i] - '0') + (num2[i] - '0') + cbit;
		rmd = sum % 10;
		cbit = sum / 10;
		res.insert(res.begin(), rmd + '0');
	}
	if (cbit > 0)
		res.insert(res.begin(), cbit + '0');
	return res;

}
int main() {

	string num1 = "4444";
	string num2 = "6666";

	int len1 = num1.size() - 1;
	int len2 = num2.size() - 1;

	string finalres = "0";//保存乘法累加结果 , 

	for (int j = len2; j >= 0; j--) {
		string res = "";//第一个数 和 第二个数的一位数 相乘的结果 
		int cbit = 0;//进位
		int rmd;//余数 

		for (int i = len1; i >= 0; i--) {
			int sum = (num1[i] - '0')*(num2[j] - '0') + cbit;
			rmd = sum % 10;
			cbit = sum / 10;
			res.insert(res.begin(), rmd + '0');//前插 0 
		}

		if (cbit > 0)
			res.insert(res.begin(), cbit + '0');//进位不是0,则要在数最前面加上进位 

		res.insert(res.end(), len2 - j, '0');//看这次乘法他跟哪位乘,跟十位乘,后面补一个0,百位补两个0 

		finalres = bAdd(finalres, res);//对第一个数 和 第二个数的一位数 相乘的结果进行累加,大数加法
	}

	cout << finalres;//输出最后的乘法累加结果 

	return 0;
}

看了以上的代码,感觉思路虽然很简单,但是实现起来却很麻烦,那么我们有没有别的方法来实现这个程序呢?答案是有的,接下来我来介绍第二种方法。

2、模拟乘法累加 - 改进

简单来说,方法二就是先不算任何的进位,也就是说,将每一位相乘,相加的结果保存到同一个位置,到最后才计算进位。

例如:计算98×21,步骤如下

        9  8
×       2  1
-------------
       (9)(8)  <----1: 98×1的每一位结果 
  (18)(16)     <----2: 98×2的每一位结果 
-------------
  (18)(25)(8)  <---- 这里就是相对位的和,还没有累加进位 

这里唯一要注意的便是进位问题,我们可以先不考虑进位,当所有位对应相加,产生结果之后,再考虑。从右向左依次累加,如果该位的数字大于10,那么我们用取余运算,在该位上只保留取余后的个位数,而将十位数进位(通过模运算得到)累加到高位便可,循环直到累加完毕。

核心代码如下:

#include <iostream>
using namespace std;
int main(){

    string str1="183",str2="171";
    int len1 = str1.size();
	int len2 = str2.size();
	
	// 分配一个空间,用来存储运算的结果,str1长的数 * str2长的数,结果不会超过str1+str2长
    int a[len1 + len2];
    fill(a, a + len1 + len2, 0);//fill()函数填充0
    
    for (int i = 0; i < len1; i++)
        for (int j = 0; j < len2; j++) 
            a[len1 + len2 - i - j - 2] += (str1[i] - '0') * (str2[j] - '0');	
			//不是-1而是-2,因为可能产生进位,留一位给最后的进位 

	//单独处理进位
    for (int i = 0; i < len1 + len2 - 1; i++){
        a[i + 1] += a[i] / 10;
        a[i] %= 10;
    }
    
    int r = len1 + len2 - 1;//逆序输出 
    while(a[r]==0 && r>0) r--;//循环排除多余0 

    for (;r >= 0;r--)
        cout<<a[r];
    return 0;
}

3、分治 - Karatsuba算法

在这里插入图片描述

首先来看看这个算法是怎么进行计算的,见 斯坦福大学 算法设计与分析,见下图:在这里插入图片描述

图中显示了计算5678 * 1234的过程,
首先是拆分成abcd四个部分,
然后分别计算ac, bd, (a+b)*(c+d)
最后再用第三个算式的结果减去前面两个(其实得到的就是bc+ad,但是减少了乘法步骤)
然后,计算式1后面加4个0,计算式2后面不加,计算式3后面加2个0,再把这三者相加,就是正确结果。

接下来,就来证明一下这个算法的正确性:
在这里插入图片描述

  • 16
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值