上一篇《关于大数值n!的讨论——习题118》中讲到:当n比较大的时候,常规类型由于取值范围限定容易造成溢出。
如何解决溢出问题呢?有两种解决方法:
其一,自己构造一个类型,接纳数据。或者采用取值范围比较大的已有类型(Java中BigIngeger和BigDecimal类型)。
其二,对阶乘乘积方式改进。
下面我将两种解决方法分别比较。
方法1:
65535!用时:91162ms
方法2:
思路是这样的:大数用一个动态数组来表示,数组的每一个元素表示一位十进制数字,高位在前,低位在后。那么,用这种表示法,如何做乘法运算呢?其实这个问题并不困难,可以使用模拟手算的方法来实现。回忆一下我们在小学时,是如何做一位数乘以多位数的乘法运算的。例如:2234*8。
我们将被乘数表示为一个数组A[], a[1],a[2],a[3],a[4]分别为2,2,3,4,a[0]置为0。
Step1: 从被乘数的个位a[4]起,取出一个数字4.
Step2: 与乘数8相乘,其积是两位数32,其个位数2作为结果的个位,存入a[4], 十位数3存入进位c。
Step3: 取被乘数的上一位数字a[3]与乘数相乘,并加上上一步计算过程的进位C,得到27,将这个数的个位7作为结果的倒数第二位,存入a[3],十位数2存入进位c。
Step4:重复Step3,取a[i](i依次为4,3,2,1)与乘数相乘并加上c,其个位仍存入a[i], 十位数字存入c,直到i等于1为止。
Step5:将最后一步的进位c作为积的最高位a[0]。
同样,求阶乘则对上述步骤改动既可。
5000!用时:5367ms
65535!用时:787156ms
这个方法相对于方法1,效率很低,但对n没限制。
如何解决溢出问题呢?有两种解决方法:
其一,自己构造一个类型,接纳数据。或者采用取值范围比较大的已有类型(Java中BigIngeger和BigDecimal类型)。
其二,对阶乘乘积方式改进。
下面我将两种解决方法分别比较。
方法1:
import java.math.BigInteger;
import java.util.Scanner;
public class Factorial {
/**
* @version 1.6 2009/11/22
* @author Eduardo
*/
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
BigInteger product = BigInteger.valueOf(1);
int n;
while (cin.hasNextInt()) {
n = cin.nextInt();
long start = System.currentTimeMillis();
for (int i = 2; i <= n; i++) {
product = product.multiply(BigInteger.valueOf(i));
}
System.out.println(product);
product = BigInteger.valueOf(1);
long end = System.currentTimeMillis();
System.out.println("Time: " + (end - start));
}
}
}
5000!用时:327msimport java.util.Scanner;
public class Factorial {
/**
* @version 1.6 2009/11/22
* @author Eduardo
*/
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
BigInteger product = BigInteger.valueOf(1);
int n;
while (cin.hasNextInt()) {
n = cin.nextInt();
long start = System.currentTimeMillis();
for (int i = 2; i <= n; i++) {
product = product.multiply(BigInteger.valueOf(i));
}
System.out.println(product);
product = BigInteger.valueOf(1);
long end = System.currentTimeMillis();
System.out.println("Time: " + (end - start));
}
}
}
65535!用时:91162ms
方法2:
思路是这样的:大数用一个动态数组来表示,数组的每一个元素表示一位十进制数字,高位在前,低位在后。那么,用这种表示法,如何做乘法运算呢?其实这个问题并不困难,可以使用模拟手算的方法来实现。回忆一下我们在小学时,是如何做一位数乘以多位数的乘法运算的。例如:2234*8。
我们将被乘数表示为一个数组A[], a[1],a[2],a[3],a[4]分别为2,2,3,4,a[0]置为0。
Step1: 从被乘数的个位a[4]起,取出一个数字4.
Step2: 与乘数8相乘,其积是两位数32,其个位数2作为结果的个位,存入a[4], 十位数3存入进位c。
Step3: 取被乘数的上一位数字a[3]与乘数相乘,并加上上一步计算过程的进位C,得到27,将这个数的个位7作为结果的倒数第二位,存入a[3],十位数2存入进位c。
Step4:重复Step3,取a[i](i依次为4,3,2,1)与乘数相乘并加上c,其个位仍存入a[i], 十位数字存入c,直到i等于1为止。
Step5:将最后一步的进位c作为积的最高位a[0]。
同样,求阶乘则对上述步骤改动既可。
import java.util.ArrayList;
import java.util.Scanner;
public class FactorialClass {
/**
* @version 1.6 2009/11/19
* @author Eduardo
*/
public void factorial(int n){
if(n<0)System.out.println(0);
if(n==1||n==0)System.out.println(1);
//定义动态数组变量
ArrayList<Integer> al = new ArrayList<Integer>();
int product,carry=0;//定义乘积、进位值
int digit=(int)(Math.ceil((0.5*Math.log(2*n*Math.PI)+n*Math.log(n)-n)/Math.log(10)));
al.add(0,1);
for(int i=2;i<=n;i++){
carry=0;
for(int j=0;j<=num;j++){
product=al.get(j)*i+carry;
al.set(j,product%10);
carry=product/10;
}
while(carry>0){
al.add(++num, carry%10);
carry/=10;
}
}
for(int i=digit-1;i>=0;i--){
System.out.print(al.get(i));
}
al.clear();
}
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
int n=0;
while(cin.hasNextInt()){
n = cin.nextInt();
long start = System.currentTimeMillis();
FactorialClass test=new FactorialClass();
test.factorial(n);
long end = System.currentTimeMillis();
System.out.println("Time: " + (end - start));
}
}
private int num=0;
}
这个方法对n的取值没有限制,不过程序中将n定义为Int型,所以取值不能超过Int型的范围,计算更大的数值只要将类型改变就可以了。import java.util.Scanner;
public class FactorialClass {
/**
* @version 1.6 2009/11/19
* @author Eduardo
*/
public void factorial(int n){
if(n<0)System.out.println(0);
if(n==1||n==0)System.out.println(1);
//定义动态数组变量
ArrayList<Integer> al = new ArrayList<Integer>();
int product,carry=0;//定义乘积、进位值
int digit=(int)(Math.ceil((0.5*Math.log(2*n*Math.PI)+n*Math.log(n)-n)/Math.log(10)));
al.add(0,1);
for(int i=2;i<=n;i++){
carry=0;
for(int j=0;j<=num;j++){
product=al.get(j)*i+carry;
al.set(j,product%10);
carry=product/10;
}
while(carry>0){
al.add(++num, carry%10);
carry/=10;
}
}
for(int i=digit-1;i>=0;i--){
System.out.print(al.get(i));
}
al.clear();
}
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
int n=0;
while(cin.hasNextInt()){
n = cin.nextInt();
long start = System.currentTimeMillis();
FactorialClass test=new FactorialClass();
test.factorial(n);
long end = System.currentTimeMillis();
System.out.println("Time: " + (end - start));
}
}
private int num=0;
}
5000!用时:5367ms
65535!用时:787156ms
这个方法相对于方法1,效率很低,但对n没限制。