参考了:
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’。