大整数乘法——算法思想及java实现

参考了:

http://blog.csdn.net/oh_maxy/article/details/10903929

但是该博主的实现并没有考虑时间效率,在博主代码的基础上了做了一点点改进,提高了时间效率。(由原来的O(n2)提高到O(n1.59))

感谢博主提供的代码。最后附上我改进的版本。

package algrithm.divideAndConquer;

import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 分治法例子:
 * 大整数乘法
 * @author tcj
 *
 */
public class TwoBigIntegerMiltiply {
		private final static int SIZE = 4;
		//进制,这里处理输入10进制的大整数(在减法函数里面用到)
		private final static int Dec = 10;
	
		//自己第一次写的,思路不清晰
/*		public static boolean IsInteger(Object obj){
			return (obj instanceof Integer);
		}
		
		public static int[] BigIntegerMlt(int[] a,int[] b,int beginA,int endA,int beginB,int endB){
			//如果输入为空
			if(a == null || b == null){
				return null;
			}
			//如果a,b或者元素为小数
			
			//如果a,b中有元素为负数
			int[] result = null;
			
			return result;
		}	*/	
		
		private static String BigIntegerMultiply(String X,String Y){
			//最终返回结果
			String  str = "";
			int len1 = X.length();
			int len2 = Y.length();
			int len = Math.max(len1, len2);
			//补齐X、Y,使之长度相同
			X = formatNum(X,len);
			Y = formatNum(Y,len);
			
			//少于4位数,可直接计算
			if(len <= SIZE){
				return "" + (Integer.parseInt(X) * Integer.parseInt(Y));
			}
			
			int len3 = len/2;
			int len4 = len - len3;
			
			//处理乘数,分块处理
			String A = X.substring(0,len3);
			String B = X.substring(len3);
			String C = Y.substring(0,len3);
			String D = Y.substring(len3);
			
			//递归求解分块
			int lenM = Math.max(len3, len4);
			String AC = BigIntegerMultiply(A,C);
			String BD = BigIntegerMultiply(B,D);
			//提高时间效率关键在这一步:
			//将AC+BD --> (A-B)(D-C)+AC+BD
			//这样仅需做3次n/2位整数的乘法,减少了一次
			//之前的总时间T(n) = 4T(n/2) + O(n) --> O(n2)
			//现在的总时间T(n) = 3T(n/2) + O(n) --> O(n1.59)
			String AMinusB  = minus(A,B);
			String DMinusC = minus(D,C);
			String ABDC = BigIntegerMultiply(AMinusB,DMinusC);
			ABDC = addition(addition(ABDC,AC),BD);
			
			//处理BD,得到原位及进位
			String[] sBD = dealString(BD,len3);
			
			//加上BD的进位
			if(!"0".equals(sBD[1])){
				ABDC = addition(ABDC,sBD[1]);
			}
			
			//处理ABDC,得到原位及进位
			String[] sABDC = dealString(ABDC,len3);
			
			//AC加上ADBC的进位
			if(!"0".equals(sABDC[1])){
				AC = addition(AC,sABDC[1]);
			}
			

			//最终结果
			str = AC + sABDC[0] + sBD[0];	
			
			//之前写错了,下面是处理二进制大整数的算法
//			String temp1 = "" + (Integer.parseInt(AC) >> len);
//			String temp2 = "" + (Integer.parseInt(ABDC) >> len3);
//			String temp3 = "" + (Integer.parseInt(AC) >> len3) ;
//			String temp4 = "" + (Integer.parseInt(BD) >> len3) + BD;
//			String temp1 = BigIntegerMultiply(AC,expOfTwo(len));
//			String temp2 = BigIntegerMultiply(ABDC,expOfTwo(len3));
//			String temp3 = BigIntegerMultiply(AC,expOfTwo(len3));
//			String temp4 = BigIntegerMultiply(BD,expOfTwo(len3));
//			String temp5 = addition(temp1,temp2);
//			String temp6 = addition(temp3,temp4);
			//最终结果
//			str = addition(temp5,temp6);
			return str;
			
		}
		
		private static String formatNum(String x,int len){
			while(len > x.length()){
				x = "0" + x;
			}
			
			return x;
		}
		
		private static String minus(String a,String b){
			int lenA = a.length();
			int lenB = b.length();	
			int len = Math.max(lenA, lenB);
			if(lenA != lenB){
				a = formatNum(a,len);
				b = formatNum(b,len);
			}
			String str = "";
			//借位,用t存储
			int flag = 0;
			
			//从后往前按位相加
			for(int i = len - 1;i >=0;i--){
			//	int t = 0;
				int tempA = a.charAt(i) - '0';
				int tempB = b.charAt(i) - '0';
				int temp;
				if(tempA - flag - tempB < 0 && i != 0){
					temp = tempA + Dec - flag - tempB;
					flag = 1;
				}else{
					temp = tempA - flag - tempB;
					flag = 0;
				}
//				if(Integer.parseInt(a.substring(i,i+1)) < Integer.parseInt(b.substring(i,i+1))){
//					flag = 1;
//					t = flag*Dec + Integer.parseInt(a.substring(i,i+1)) - Integer.parseInt(b.substring(i,i+1));		
	
//				}else{
//					flag = 0;
//					t = Integer.parseInt(a.substring(i,i+1)) - Integer.parseInt(b.substring(i,i+1));	
//				}
				
				str = "" + temp+ str;
			}
			
			//去掉前面的0
			while(str.length() > 1 && str.charAt(0) == '0'){
				str = str.substring(1);
			}
			
			return str;
		}
		
		//两个数字串按位加
		private static String addition(String a,String b){
			int lenA = a.length();
			int lenB = b.length();	
			int len = Math.max(lenA, lenB);
			if(lenA != lenB){
				a = formatNum(a,len);
				b = formatNum(b,len);
			}
			String str = "";
			//进位,用t存储
			int flag = 0;
			
			//从后往前按位相加
			for(int i = len - 1;i >= 0;i--){
				
				int t = flag + Integer.parseInt(a.substring(i,i+1)) + Integer.parseInt(b.substring(i,i+1));
				
				if(t > 9){
					flag = 1;
					t -= 10;
				}else{
					flag = 0;
				}
				
				str = "" + t + str;
			}
			//最高位是否有进位
			if(flag != 0){
				str = "" + flag + str;
			}
			
			return str;
		}
		
		private static String[] dealString(String ac,int len){
			String[] str = {ac,"0"};
			if(ac.length() > len){
				int t = ac.length() - len;
				str[0] = ac.substring(t);
				str[1] = ac.substring(0,t);
			}
			else{
				//要保证结果的length与入参len一致,少于则高位补0
				String result = str[0];
				for(int i = result.length();i < len;i++){
					result = "0" + result;
				}
				str[0] = result;
			}
			return str;
		}
		private static String  expOfTwo(int len){
			int result = 1;
			for(int i = 0;i < len;i++){
				result *= 2; 
			}
			return result+"";
		}
		public static void main(String[] args){
			//正则表达式,不以0开头的数字串
			String pat = "^[1-9]\\d*$";
			Pattern p = Pattern.compile(pat);
			
			//获得乘数A
			System.out.println("请输入乘数A(不以0开头的正整数):");
			Scanner sc = new Scanner(System.in);
			String A = sc.nextLine();
			Matcher m = p.matcher(A);
			if(!m.matches()){
				System.out.println("数字不合法!");
				return;
			}
			
			//获得乘数B
			System.out.println("请输入乘数B(不以0开头的正整数:");
			sc = new Scanner(System.in);
			String B = sc.nextLine();
			m = p.matcher(B);
			if(!m.matches()){
				System.out.println("数字不合法!");
				return;
			}
			
//		System.out.println(minus("320","139"));
			System.out.println(A + "*" +B + " = "
					+ BigIntegerMultiply(A,B));
//			System.out.println(
//					 BigIntegerMultiply("1234","5678"));
					
		}

}

最后的最后,为了方便自己以后查看,附上原博主的分析:

大整数乘法,就是乘法的两个乘数比较大,最后结果超过了整型甚至长整型的最大范围,此时如果需要得到精确结果,就不能常规的使用乘号直接计算了。没错,就需要采用分治的思想,将乘数“分割”,将大整数计算转换为小整数计算。

在这之前,让我们回忆一下小学学习乘法的场景吧。个位数乘法,是背诵乘法口诀表度过的,不提也罢;两位数乘法是怎么做的呢?现在就来一起回忆下12*34吧:
   3  4  (是不是很多小朋友喜欢将大的整数作为被乘数的,呵呵)
 *1  2 
———
   6  8
3 4
———
4 0  8

接下来就是找规律了。其实大家多做几个两位数的乘法,都会发现这个规律:AB * CD = AC (AD+BC) BD 。没错,这里如果BD相乘超过一位数,需要进位(这里二四得八,没有进位);(AD+BC+低位进位)如果超过一位数,需要进位(就像刚才的3*1,最后+1得4的操作)。

到这里可以看出,我们任意位数的整数相乘,最终都是可以转化为两位数相乘。当然,不同位的两位数乘的结果,最后应该如何拼接呢?这需要我们来找下更深层次的规律了。这下来个四位数的乘法,找找感觉:
             1  2  3  4
          *  5  6  7  8
————————
              9  8  7  2
          8  6  3  8
      7  4  0  4
 6  1  7  0
————————
 7  0  0  6  6  5  2

这个结果看起来也没什么特别的,如果按照我们分治的思想,转换为两位数相乘,之间能否有些关系呢?
1234分为 12(高位)和34(低位);5678分为56(高位)和78(低位)
高位*高位结果:12*56=672
高位*低位+低位*高位:12*78+34*56=936+1094=2840
低位*低位结果:34*78=2652

这里要拼接了。需要说明的是,刚才我们提到两位数分解成一位数相乘的规则:超过一位数,需要进位。同理(这里就不证明了),两位数乘以两位数,结果超过两位数,也要进位。
从低位开始:低两位:2652,26进位,低位保留52;中间两位,2840+26(低位进位)=2866,28进位,中位保留66;高位,672+28(进位)=700,7进位,高位保留00。再往上就没有了,现在可以拼接起来:最高位进位7,高两位00,中位66,低位52,最后结果:7006652。

规律终于找到了!任意位数(例如N位整数相乘),都可以用这种思想实现:低位保留N位数字串,多余高位进位;高位要加上低位进位,如果超过N位,依然只保留N位,高位进位。(如果是M位整数乘以N位整数怎么办?高位补0,凑成一样位数的即可,不赘述。)

分治的规律找到了,接下来就是具体实现的思想了。
没啥新花样,依然是递归思想(这里为了简化,就不递归到两位数相乘了,4位数相乘,计算机还是能够得到精确值的):
1. 如果两个整数M和N的长度都小于等于4位数,则直接返回M*N的结果的字符串形式。
2. 如果如果M、N长度不一致,补齐M高位0(不妨设N>M),使都为N位整数。
3. N/2取整,得到整数的分割位数。将M、N拆分成m1、m2,n1,n2。
4. 将m1、n1;m2、n1;m1、n2;m2、n2递归调用第1步,分别得到结果AC(高位)、BC(中位)、AD(中位)、BD(低位)。
5. 判断BD位是否有进位bd,并截取bd得到保留位BD’;判断BC+AD+bd是否有进位abcd,并截取进位得到保留位ABCD’;判断AC+abcd是否有进位ac,并截取进位得到保留位AC’。
6. 返回最终大整数相乘的结果:ac AC’ ABCD’ BD’。

    原文作者:大整数乘法问题
    原文地址: https://blog.csdn.net/taochangchang/article/details/38955897
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞