这是我的程序,但是对于10万这样的大数字,它的运行速度非常慢,是否有优化的选择?
import java.math.BigInteger;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
BigInteger sum = BigInteger.valueOf(1);
for (BigInteger i = BigInteger.valueOf(n);
i.compareTo(BigInteger.ZERO) > 0;
i = i.subtract(BigInteger.ONE)) {
sum = sum.multiply(i);
}
System.out.println(sum);
}
}
答案 0 :(得分:2)
只是为了说明有时它需要操纵表达式,我修改了计算1 * 2 * 3 * ... * n的标准乘法循环,将其分为两部分:一部分将奇整数相乘(1 * 3 * 5 * ...),另一个将偶数相乘(2 * 4 * 6 * ...)。通过将0 mod 2但 not 0 mod 4(例如2 * 6 * 10 * ...)的偶数相乘,再将0 mod 4的偶数乘以 not 0 mod 8(例如4 * 12 * 20 * 28 * ...),依此类推,但是2的幂首先移出该数字。将两个的幂加起来,然后将乘积最后一次向左移。这利用了Java 8 BigInteger的实现方式,可以使较大的左移相当有效。
private static BigInteger fac4(int n) {
BigInteger evens = multiplyEvens(n);
BigInteger odds = multiplyOdds(n);
BigInteger product = evens.multiply(odds);
return product;
}
private static BigInteger multiplyOdds(int n) {
BigInteger odds = BigInteger.ONE;
for (long i=1; i<=n; i+=2) {
odds = odds.multiply(BigInteger.valueOf(i));
}
return odds;
}
private static BigInteger multiplyEvens(int n) {
BigInteger evens = BigInteger.ONE;
long pow2 = 1;
int shiftAmount = 0;
while ((1 << pow2) <= n) {
for (long i = (1<<pow2); i <= n; i += (1 << (pow2 + 1))) {
shiftAmount += pow2;
evens = evens.multiply(BigInteger.valueOf(i >> pow2));
}
++pow2;
}
return evens.shiftLeft(shiftAmount);
}
public static void main(String[] args) {
// Print out some small factorials to verify things are working
for (int i = 0; i < 10; i++) {
System.out.printf("%d! = %d%n", i, fac4(i));
}
Scanner in = new Scanner(System.in);
int n = in.nextInt();
long start = System.currentTimeMillis();
BigInteger fac = fac4(n);
long end = System.currentTimeMillis();
float total = end - start;
System.out.printf("%d! is %d bits long, took %f seconds to compute", n, fac.bitLength(), total / 1000);
}
这是一次n = 100000的输入/输出日志:
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
100000
100000! is 1516705 bits long, took 1.758000 seconds to compute
为了进行比较,我实现简单的多重循环大约需要3秒钟。
编辑:
这是我尝试过的另一个实现,它甚至更快。这个想法是利用Java 8+ BigInteger的渐近速度比O(n 2 )算法快的事实,而乘法的操作数变得足够大以提供优势。但是,幼稚的方法总是将单个“肢体”整数乘以快速增长的累积乘积。这种方法不适用于更快的算法。但是,如果我们乘以近似相等的操作数,则可以使用更快的算法。
private static final int SIMPLE_THRESHOLD = 10;
private static BigInteger fac6(int n) {
return subfac(1, n);
}
/**
* compute a * (a+1) * ... *(b-1) * b
* The interval [a,b] includes the endpoints a and b.
*
* @param a the interval start.
* @param b the interval end, inclusive.
* @return the product.
*/
private static BigInteger subfac(int a, int b) {
if ((b-a) < SIMPLE_THRESHOLD) {
BigInteger result = BigInteger.ONE;
for (int i=a; i<=b; i++) {
result = result.multiply(BigInteger.valueOf(i));
}
return result;
} else {
int mid = a + (b-a) / 2;
return subfac(a, mid).multiply(subfac(mid+1, b));
}
}
使用与上述相同的main()
方法的输出为:
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
100000
100000! is 1516705 bits long, took 0.243000 seconds to compute
因此fac6()
比fac4()
快10倍。一些实验表明,SIMPLE_THRESHOLD的值对速度几乎没有影响,大概是因为函数调用的开销与BigInteger乘法的开销相形见。
所有这些实验都是使用JDK 1.8.0_181在Mac OS X High Sierra笔记本电脑上运行的。
答案 1 :(得分:0)
这是我的第一个明显的实现:
public static void main(String[] args) {
long start = System.currentTimeMillis();
int n = 100000;
BigInteger bigInteger = BigInteger.ONE;
for (int i = 1; i < n; i++) {
bigInteger = bigInteger.multiply(BigInteger.valueOf(i));
}
System.out.println(bigInteger);
long end = System.currentTimeMillis();
float total = end - start;
System.out.println(total);
}
100000的基准是一个456569位的数字(因此我在这里不能打印出来),我的解决方案大约需要3.5秒。
如果这对您来说不是不可能的,那么您必须设计一个基于多线程的解决方案。例如,一个线程将n
的前一半相乘,而另一个线程则将后一半相乘。然后,将这两个数字相乘。