大整数相乘问题总结以及Java实现

最近在跟coursera上斯坦福大学的算法专项课,其中开篇提到了两个整数相乘的问题,其中最简单的方法就是模拟我们小学的整数乘法,可想而知这不是比较好的算法,这门课可以说非常棒,带领我们不断探索更优的算法,然后介绍可以通过使用分而治之的思想来解决这个问题。下面对该问题的方法以及实现进行介绍。

问题定义

输入:2个n位的整数x和y
输出:x * y

如求: 1234567891011121314151617181*2019181716151413121110987654 的结果

求解该问题要注意的是由于整数的位数可能超过基本类型的表示范围,所以一种方式是将其转化为字符串进行表示,另一种方式是可以使用一些语言自带的大整数类型(如Java的BigInteger)。参考一些资料才发现,该问题的解法其实有很多种,主要列举以下:

1.模拟小学乘法: 竖式乘法累加。

2.分治乘法: 最简单的是Karatsuba乘法,一般化以后有Toom-Cook乘法.

3.快速傅里叶变换

4.中国剩余定理

我们主要介绍模拟乘法累加以及使用分治思想的Karatsuba乘法,最后使用Java进行实现。

模拟小学乘法

      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趟
-----------------
  ? ? ? ? ? ? ? ?   <---- 最后的值用另一个数组表示

如上所示,需要将乘数与被乘数逐位相乘,最后再进行累加,时间复杂度为{O(n^2)}.模拟乘法累加还有一个改进版,上述方法在实现时,每次计算乘法都需要考虑进位,最后在加法时也需要进位,比较麻烦。一种改进的版本如下:

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

改进的方法先不算任何的进位,也就是说,将每一位相乘,相加的结果保存到同一个位置,到最后才计算进位。我们可以先将结果保存到一个数组中(不考虑进位),最后对数组从右向左进行遍历,大于10进行进位。Java实现如下:

public class BigNumMul {
    //simple method 模拟乘法累加
    public static String bigNumberMul(String num1, String num2) {
        // 分配一个空间,用来存储运算的结果,num1长的数 * num2长的数,结果不会超过num1+num2长
        int[] res = new int[num1.length() + num2.length()];
        // 先不考虑进位问题,根据竖式的乘法运算,num1的第i位与num2的第j位相乘,结果应该存放在结果的第i+j位上
        for (int i=0; i<num1.length(); i++) {
            int a = num1.charAt(i) - ‘0‘;
            for (int j=0; j<num2.length(); j++) {
                int b = num2.charAt(j) - ‘0‘;
                res[i+j] += a * b;      //max: num1.length()+num2.length()-2
            }
        }
        StringBuilder sb = new StringBuilder();
        //单独处理进位
        int carry = 0;
        //最多就到res.length-2, 最后一个元素没有被占用,还是初始值0
        for (int k=res.length-2; k >= 0; k--) {
            int digit = (res[k] + carry) % 10;
            carry = (res[k] + carry) / 10;
            sb.insert(0, digit);
        }
        if (carry > 0) {
            sb.insert(0, carry);
        }

        String str = sb.toString().replaceFirst("^0*", "");

        return str.substring(0,str.length()-1);
    }
}

显然使用O(n^2)的算法是不够好的,我们应该想一下有没有更好的算法,就像这门课上经常说的一句:Can we do better ?

分治:Karatsuba算法

分治算法的主要思想是能将问题分解为输入规模更小的子问题,然后递归求解子问题,最后将子问题的结果合并得到原问题的结果,最典型的如归并排序算法。为了得到规模更小的子问题,就要将较大的整数拆分为位数较少的两个整数,参考coursera上的算法专项课,主要计算过程如下:

如上图所示,将每个数分别拆分为两部分,分别计算ac, bd, 以及(a+b)(c+d),最后再减去前面两个,将其组合成最终的结果。我们采用更一般的方式将其表达出来,相应计算方法如下:

上述给出了更通用的写法,将x和y同分解后的更小的整数进行表示,最后通过递归的计算ac, ad, bc, bd就可以得到x*y的结果。上述是没有优化过的分治算法,每次递归需要4次乘法,合并结果需要O(n)时间复杂度,所以可以得到时间复杂度的表示:

{T(n) = 4T(n/2) + O(n)}

通过主方法,可以求得上述时间复杂度为O(n^2),并没有得到好的改善。

Karatsuba算法将上述的4次乘法优化为3次从而减少了时间复杂度。具体过程如下:

可以看到上述利用(a+b)(c+d)的结果减去ac和bd得到ad+bc的结果,从而只需要计算三次乘法,其时间复杂度可以表示为:

T(n)=3T(n/2)+6n=O(n^{log_{2}3})

根据上述算法,使用Java进行实现代码如下:

    //Karatsuba乘法
    //此种情况使用long时,数过大可能出现越界,应考虑使用BigInteger
    public static long karatsuba(long num1, long num2) {
        //递归终止条件
        if (num1 < 10 || num2 < 10) {
            return num1 * num2;
        }

        // 计算拆分长度
        int size1 = String.valueOf(num1).length();
        int size2 = String.valueOf(num2).length();
        int halfN = Math.max(size1, size2) / 2;

        /* 拆分为a, b, c, d */
        long a = Long.valueOf(String.valueOf(num1).substring(0, size1-halfN));
        long b = Long.valueOf(String.valueOf(num1).substring(size1-halfN));
        long c = Long.valueOf(String.valueOf(num2).substring(0, size2-halfN));
        long d = Long.valueOf(String.valueOf(num2).substring(size2-halfN));

        // 计算z2, z0, z1, 此处的乘法使用递归
        long z1 = karatsuba(a, c);
        long z2 = karatsuba(b, d);
        long z3 = karatsuba((a + b), (c + d)) - z1 - z2;

        return (long)(z1 * Math.pow(10, 2*halfN) + z2 + z3 * Math.pow(10, halfN));
    }

注意上述递归的终止条件以及如何表示a, b, c, d. 上述实现使用的是Java中的long类型,但是当整数变大时,使用long类型可能会发生溢出,这里可以使用String来模拟整数的加法及乘法,或者使用Java的BigInteger类型,其实BigInteger内部也是使用的String进行存储,我使用的是BigInteger类型,实现代码如下:

   //使用BigInteger的karatsuba算法
    //注意BigInteger的运算没有操作符重载
    //参考: coursera算法专项1
    public static BigInteger karatsuba(BigInteger num1, BigInteger num2) {
        if (num1.compareTo(BigInteger.valueOf(10)) < 0 || num2.compareTo(BigInteger.valueOf(10)) < 0) {
            return num1.multiply(num2);
        }

        int n = Math.max(num1.toString().length(), num2.toString().length());
        int halfN = n / 2 + n % 2;  //另一种划分方法

        //返回num1 / halfN 和 num1 % halfN
        BigInteger[] a_b = num1.divideAndRemainder(BigInteger.valueOf(10).pow(halfN));
        BigInteger a = a_b[0];
        BigInteger b = a_b[1];
        BigInteger[] c_d = num2.divideAndRemainder(BigInteger.valueOf(10).pow(halfN));
        BigInteger c = c_d[0];
        BigInteger d = c_d[1];

        BigInteger step1 = karatsuba(a, c);
        BigInteger step2 = karatsuba(b, d);
        BigInteger step3 = karatsuba(a.add(b), c.add(d));
        BigInteger step4 = step3.subtract(step2).subtract(step1);   //step3-step2-step1
        BigInteger res = step1.multiply(BigInteger.valueOf(10).pow(2*halfN)).add(step2)
                .add(step4.multiply(BigInteger.valueOf(10).pow(halfN)));

        return res;
    }

最后的测试代码如下:

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String a = sc.next();
        String b = sc.next();

        // 开始计算
        //String str = BigNumMul.bigNumberMul(a, b);
        //long res = BigNumMul.karatsuba(Long.valueOf(a), Long.valueOf(b));
        //String str = Long.toString(res);
        BigInteger res = BigNumMul.karatsuba(new BigInteger(a), new BigInteger(b));
        String str = res.toString();
        System.out.println(a + " * " + b + " = " + str);
    }

总结与感想

(1)对一个问题要深入调研和分析,多尝试不同的解决方法。

(2)可以多分析一些诸如此类的经典问题,还是比较有意思的。

参考资料:

1.coursera算法专项

2.大数乘法问题及其高效算法

3.https://stackoverflow.com/questions/17531042/karatsuba-algorithm-without-biginteger-usage

4.https://chenyvehtung.github.io/2017/03/02/about-multiplication.html

原文地址:https://www.cnblogs.com/litexy/p/9744374.html

时间: 2024-10-08 05:17:32

大整数相乘问题总结以及Java实现的相关文章

求两个大整数相乘的积,数字长度在127个字符之内。

计算两个大整数相乘的结果,数字不是double类型能表示的,数字长度没有限制(最大127位). 方法一:使用java中的大整数处理库函数,java.math.BigInteger,java.math.BigDecimal; 方法二:不利用库函数,自己实现其功能,可以练习自己对待复杂问题的处理能力,提高编程水平. 此代码中利于两者的结合,利于库函数可以看自己编写的代码的正确性. 1.主函数 import java.math.BigDecimal; import java.util.Scanner;

用阿拉伯乘法解决大整数相乘问题

问题 大整数相乘 思路说明 对于大整数计算,一般都要用某种方法转化,否则会溢出.但是python无此担忧了. Python支持"无限精度"的整数,一般情况下不用考虑整数溢出的问题,而且Python Int类型与任意精度的Long整数类可以无缝转换,超过Int 范围的情况都将转换成Long类型. 例如: >>> 2899887676637907866*1788778992788348277389943 5187258157415700236034169791337062

[算法]:分治法-求大整数相乘

#问题大整数相乘 #思路说明 对于大整数计算,一般都要用某种方法转化,否则会溢出.但是python无此担忧了. Python支持**"无限精度"的整数,**一般情况下不用考虑整数溢出的问题,而且Python Int类型与任意精度的Long整数类可以无缝转换,超过Int 范围的情况都将转换成Long类型. 例如: >>> 2899887676637907866*1788778992788348277389943 51872581574157002360341697913

python写的大整数相乘的方法

输入 72106547548473106236 982161082972751393 两个大整数 输出结果 70820244829634538040848656466105986748 解题思路 首先根据 大整数相乘的原理的基础上,把大整数进行优化拆分,拆分的长度,要考虑语言中整形的长度.这里用的python,其实可以直接乘的,呵呵.我暂定设为了 4,也就是 说 7210 6547 5484 7310 6236 98 2161 0829 7275 1393 然后 逐份相乘,当然千万别忘了结果要补

python实现大整数相乘---格子乘法

以前做ACM的时候,许多人都通过 BigInteger 来实现大数乘法,让我记忆犹新的事2012年的辽宁省赛在大连大学,第一道水题就是大整数乘法,那时还不会java. 大数乘法的实现是基于印度的格子乘法,使用这种方法,计算 m 位数乘以 n 位数只需要创建一个 m+n 位的数组保存结果即可. 今天我们来用python来模拟一下格子算法的运算过程,python来写算法还是很简单的. 下面是从维基百科上爬下来的图片和详细步骤. 第一步:画带斜线的格子,将第一数(58)写在格子顶部,第二数(213)书

大整数相乘“分治法”和“循环暴力法”

前言 今天刷到一道很有趣的面试题,感觉很有意思,来分享给大家. 题目描述 有两个用字符串表示的非常大的大整数,算出他们的乘积,也是用字符串表示.不能用系统自带的大整数类型. 输入描述:空格分隔的两个字符串,代表输入的两个大整数输出描述:输入的乘积,用字符串表示 示例1 输入72106547548473106236 982161082972751393输出70820244829634538040848656466105986748 思路分析 例如x=1234,y=567 ①将x拆分成两半儿,a =

链表问题3——大整数相乘

Description 求两个不超过200位的非负整数的积. 解决方法:先考虑大整数存储结构,数组和链表都可以,因为数组使用不熟练,我先考虑链表,做了6小时才写好...也是醉了.下面简单介绍我的思路: 1.考虑链表的数据结构:先思考得知,链表结点要有pre.next指针,所以是双向链表,要设置头尾节点,我是让尾结点指向最后一个结点,头节点为虚结点. 2.链表构造问题:从输入的字符串转换为整型的链表 3.相乘:为了错位相加中运算方便,我将结果倒序表示,头节点后面连结果的最后一位数.一开始结果链表r

[Nowcoder] 大整数相乘(拼多多笔试题)

有两个用字符串表示的非常大的大整数,算出他们的乘积,也是用字符串表示.不能用系统自带的大整数类型. 输入描述: 空格分隔的两个字符串,代表输入的两个大整数 输出描述: 输入的乘积,用字符串表示 输入例子1: 72106547548473106236 982161082972751393 输出例子1: 70820244829634538040848656466105986748 大数乘法问题 简单来说就是把A[i]*B[j]累加放到临时数组的C[i+j]位置. 然后对数组C执行进位加法即可. 剩下

数组乘法(大整数相乘)

题目:Power of Cryptography 解法:https://blog.csdn.net/code_pang/article/details/8263971 题目类型:分治(大概吧,也不是很确定) 知识点:pow()函数的底层运算机制(因为这道题居然居然可以用一个函数就AC掉!!??),数字位数的确定,数组相乘法(即两个超大整数的相乘,只能用数组存储),二分查找法(节省时间,毕竟题目给的时间只有1秒!) 题目:牛课网上的https://www.nowcoder.com/acm/cont