【问题标题】:Calculating the product of BigInteger[]计算 BigInteger[] 的乘积
【发布时间】:2015-07-22 17:37:06
【问题描述】:

上下文:我正在尝试使用 Java 中的 BigInteger 类计算非常大的 n 的阶乘(对于 n>100,000),到目前为止,这就是我正在做的事情:

  • 使用 Erathones 筛产生所有小于或等于 n 的素数

  • 找出他们将被提升到哪些权力。

  • 将所有数字提高到各自的幂。

  • 使用分而治之的递归方法将它们全部相乘。

根据我在互联网上所做的研究,这比简单地将所有 k 乘以 n 渐近更快。但是我注意到我的实现中最慢的部分是我乘以所有素数的部分。我的问题是:

  • 有没有更快的方法来计算大量数字的乘积?
  • 我的实现可以改进吗?

代码:

public static BigInteger product(BigInteger[] numbers) {
    if (numbers.length == 0)
        throw new ArithmeticException("There is nothing to multiply!");
    if (numbers.length == 1)
        return numbers[0];
    if (numbers.length == 2)
        return numbers[0].multiply(numbers[1]);

    BigInteger[] part1 = new BigInteger[numbers.length / 2];
    BigInteger[] part2 = new BigInteger[numbers.length - numbers.length / 2];
    System.arraycopy(numbers, 0, part1, 0, numbers.length / 2);
    System.arraycopy(numbers, numbers.length / 2, part2, 0, numbers.length - numbers.length / 2);

    return product(part1).multiply(product(part2));
}
  • 请注意,BigInteger 使用 karatsuba 算法进行乘法运算。
  • 我知道有很多关于计算阶乘的问题。但我的任务是计算没有太多资源的 BigIntegers 的乘积。 (我看到有人说“使用分而治之的方法”,但我不记得在哪里,我还没有看到任何实现。

【问题讨论】:

    标签: java algorithm biginteger multiplication


    【解决方案1】:

    提高性能的一种方法是执行以下操作:

    1. 对需要相乘的数字数组进行排序
    2. 创建两个新列表:ab
    3. 对于输入列表中需要相乘的每个数字,它可能会出现多次。假设数字v_i 出现n_i 次。然后将v_i 添加到a n_i / 2 次(向下舍入)。如果 n_i 是奇数,请将 v_i 添加到 b 一次。
    4. 要计算结果,请执行以下操作:
    BigInteger A = product(a);
    BigInteger B = prudoct(b);
    return a.multiply(a).multiply(b);
    

    要了解它是如何工作的,请考虑您的输入数组是 [2, 2, 2, 2, 3, 3, 3]。所以,有四个 2 和三个 3。数组ab 将对应

    a = [2, 2, 3]
    b = [3]
    

    然后您将递归调用计算这些的乘积。请注意,我们将要相乘的数字数量从 7 减少到 4,几乎减少了两倍。这里的诀窍是,对于多次出现的数字,我们只能计算其中一半的乘积,然后将其提高到 2 的幂。非常类似于如何在O(log n) 时间内计算数字的幂。

    【讨论】:

    • 谢谢。我同意如果产品多次具有相同的编号,则该方法可以更有效;但不只是使用 pow 函数比这更好吗?而且我仍然不确定列表排序的效果。
    • 排序是为了更容易找到相等的元素(然后从左到右只需一次扫描即可将相等的元素组合在一起),但您可以使用任何其他方式来实现这一点。关于 pow,是的,复杂性是相同的,但是我的方法的乘法总数略低。考虑一个有 8 个二和 8 个三的情况。你需要 3 次乘法来计算 2^8,3 次乘法来计算 3^8 和 1 来将它们相乘,总共 7。使用我的方法,将有一个乘法来计算 2 * 3,然后再增加三个来提高它8次方,共4次。
    • 哦。我懂了。就像你说的,它类似于乘以两个幂。我将尝试实施它。再次感谢!
    【解决方案2】:

    我提出另一个思路,pow算法非常快,可以用指数计算所有素数,像这样:

    11! -> {2^10, 3^5, 5^2, 7^1, 11^1}
    

    您可以计算所有素数的幂,然后使用分治法将所有素数相乘。 实现:

    private static BigInteger divideAndConquer(List<BigInteger> primesExp, int min, int max){
        BigInteger result = BigInteger.ONE;
        if (max - min == 1){
            result = primesExp.get(min);
        } else if (min < max){
            int middle = (max + min)/2;
            result = divideAndConquer(primesExp, min, middle).multiply(divideAndConquer(primesExp, middle, max));
        }
        return result;
    }
    
    public static BigInteger factorial(int n) {
        // compute pairs: prime, exp
        List<Integer> primes = new ArrayList<>();
        Map<Integer, Integer> primeTimes = new LinkedHashMap<>();
        for (int i = 2; i <= n; i++) {
            int sqrt = Math.round((float) Math.sqrt(i));
            int value = i;
            Iterator<Integer> it = primes.iterator();
            int prime = 0;
            while (it.hasNext() && prime <= sqrt && value != 0) {
                prime = it.next();
                int times = 0;
                while (value % prime == 0) {
                    value /= prime;
                    times++;
                }
                if (times > 0) {
                    primeTimes.put(prime, times + primeTimes.get(prime));
                }
            }
            if (value > 1) {
                Integer times = primeTimes.get(value);
                if (times == null) {
                    times = 0;
                    primes.add(value);
                }
                primeTimes.put(value, times + 1);
            }
        }
        // compute primes power:
        List<BigInteger> primePows = new ArrayList<>(primes.size());
        for (Entry<Integer,Integer> e: primeTimes.entrySet()) {
            primePows.add(new BigInteger(String.valueOf(e.getKey())).pow(e.getValue()));
        }
        // it multiply all of them:
        return divideAndConquer(primePows, 0, primePows.size());
    }
    

    【讨论】:

      【解决方案3】:

      可能是最快的方法:

      Sequence.java

      import java.math.BigInteger;
      import java.util.ArrayList;
      import java.util.Collections;
      import java.util.List;
      
      public final class Sequence {
      
          private final List<BigInteger> elements;
      
          private Sequence(List<BigInteger> elements) {
              this.elements = elements;
          }
      
          public List<BigInteger> getElements() {
              return elements;
          }
      
          public int size() {
              return elements.size();
          }
      
          public Sequence subSequence(int startInclusive, int endExclusive) {
              return subSequence(startInclusive, endExclusive, false);
          }
      
          public Sequence subSequence(int startInclusive, int endExclusive, boolean sync) {
              return Sequence.of(elements.subList(startInclusive, endExclusive), sync);
          }
      
          public void addLast(BigInteger element) {
              elements.add(element);
          }
      
          public BigInteger removeLast() {
              return elements.remove(size() - 1);
          }
      
          public BigInteger sum() {
              return sum(false);
          }
      
          public BigInteger sum(boolean parallel) {
              return parallel
                      ? elements.parallelStream().reduce(BigInteger.ZERO, BigInteger::add)
                      : elements.stream().reduce(BigInteger.ZERO, BigInteger::add);
          }
      
          public BigInteger product() {
              return product(false);
          }
      
          public BigInteger product(boolean parallel) {
              return parallel
                      ? elements.parallelStream().reduce(BigInteger.ONE, BigInteger::multiply)
                      : elements.stream().reduce(BigInteger.ONE, BigInteger::multiply);
          }
      
          public static Sequence range(int startInclusive, int endExclusive) {
              return range(startInclusive, endExclusive, false);
          }
      
          public static Sequence range(int startInclusive, int endExclusive, boolean sync) {
              if (startInclusive > endExclusive) {
                  throw new IllegalArgumentException();
              }
              final List<BigInteger> elements = sync ? Collections.synchronizedList(new ArrayList<>()) : new ArrayList<>();
              for (; startInclusive < endExclusive; startInclusive++) {
                  elements.add(BigInteger.valueOf(startInclusive));
              }
              return new Sequence(elements);
          }
      
          public static Sequence of(List<BigInteger> elements) {
              return of(elements, false);
          }
      
          public static Sequence of(List<BigInteger> elements, boolean sync) {
              return new Sequence(sync ? Collections.synchronizedList(elements) : elements);
          }
      
          public static Sequence empty() {
              return empty(false);
          }
      
          public static Sequence empty(boolean sync) {
              return of(new ArrayList<>(), sync);
          }
      
      }
      

      FactorialCalculator.java

      import java.math.BigInteger;
      import java.util.LinkedList;
      import java.util.List;
      
      public final class FactorialCalculator {
      
          private static final int CHUNK_SIZE = Runtime.getRuntime().availableProcessors();
      
          public static BigInteger fact(int n) {
              return fact(n, false);
          }
      
          public static BigInteger fact(int n, boolean parallel) {
              if (n < 0) {
                  throw new IllegalArgumentException();
              }
              if (n <= 1) {
                  return BigInteger.ONE;
              }
              Sequence sequence = Sequence.range(1, n + 1);
              if (!parallel) {
                  return sequence.product();
              }
              sequence = parallelCalculate(splitSequence(sequence, CHUNK_SIZE * 2));
              while (sequence.size() > CHUNK_SIZE) {
                  sequence = parallelCalculate(splitSequence(sequence, CHUNK_SIZE));
              }
              return sequence.product(true);
          }
      
          private static List<Sequence> splitSequence(Sequence sequence, int chunkSize) {
              final int size = sequence.size();
              final List<Sequence> subSequences = new LinkedList<>();
              int index = 0, targetIndex;
              while (index < size) {
                  targetIndex = Math.min(index + chunkSize, size);
                  subSequences.add(sequence.subSequence(index, targetIndex, true));
                  index = targetIndex;
              }
              return subSequences;
          }
      
          private static Sequence parallelCalculate(List<Sequence> sequences) {
              final Sequence result = Sequence.empty(true);
              sequences.parallelStream().map(s -> s.product(true)).forEach(result::addLast);
              return result;
          }
      
      }
      

      测试:

      public static void main(String[] args) {
          // warm up
          for (int i = 0; i < 100; i++) {
              FactorialCalculator.fact(10000);
          }
          int n = 1000000;
          long start = System.currentTimeMillis();
          FactorialCalculator.fact(n, true);
          long end = System.currentTimeMillis();
          System.out.printf("Execution time = %d ms", end - start);
      }
      

      结果:

      Execution time = 3066 ms
      
      • 操作系统:Win 10 Pro 64 位
      • CPU:英特尔酷睿 i7-4700HQ @ 2.40GHz 2.40GHz

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-10-10
        • 1970-01-01
        • 2018-10-06
        • 2017-05-13
        • 1970-01-01
        • 2021-12-16
        • 2023-01-03
        • 1970-01-01
        相关资源
        最近更新 更多