【问题标题】:Fastest way to calculate binomial coefficients in Java在 Java 中计算二项式系数的最快方法
【发布时间】:2019-12-27 03:11:20
【问题描述】:

我正在使用以下递归函数计算任意大小的二项式系数

private static final BigDecimal ZERO = new BigDecimal("0");
private static final BigDecimal ONE = new BigDecimal("1");


private static BigDecimal binomial(BigDecimal n, BigDecimal k) {
     if(n.equals(k) || k.equals(ZERO))
          return ONE;
     else if(k.compareTo(ZERO) < 0)
          return ZERO;
     else
          return binomial(n.subtract(ONE), k).add(binomial(n.subtract(ONE), k.subtract(ONE)));
}

对于大量数据,它变得非常慢。是否有任何简单和/或明显的优化?不确定 BigDecimals 会减慢多少,但为大数创建自定义类似乎需要做很多工作。

【问题讨论】:

  • 保留中间结果并使用它们,而不是每次递归时都重新计算。
  • 不要重新计算已有的值,而是存储它们。
  • 你读过these answers吗?它们适用于 C#,但应该很容易适应。这种递归算法本质上很慢。
  • 我倾向于认为BigInteger 会比BigDecimal. 更有效率

标签: java performance kotlin optimization binomial-coefficients


【解决方案1】:

你可以在保持递归的同时做得相当好(尽管BigInteger 上的算术非常不愉快):

public class Binomials {
    private HashMap<Pair<BigInteger, BigInteger>, BigInteger> map = new HashMap();

    public BigInteger binomial(int n, int k) {
        return binomial(new Pair(valueOf(n), valueOf(k)));
    }

    public BigInteger binomial(Pair<BigInteger, BigInteger> x) {
        if(x.getValue().equals(ZERO) || x.getKey().equals(x.getValue())) {
            return ONE;
        }
        return map.computeIfAbsent(x, nk -> binomial(doP1(nk)).add(binomial(doP2(nk))));
    }

    private Pair<BigInteger, BigInteger> doP1(Pair<BigInteger, BigInteger> nk) {
        return new Pair(nk.getKey().subtract(ONE), nk.getValue());
    }
    private Pair<BigInteger, BigInteger> doP2(Pair<BigInteger, BigInteger> nk) {
        return new Pair(nk.getKey().subtract(ONE), nk.getValue().subtract(ONE));
    }

    public static void main(String[] args) {
        System.out.println(new Binomials().binomial(8, 4)); // 70
    }
}

事实上,所有PairBigInteger 的恶作剧都足以掩盖正在发生的事情,所以这里是 Kotlin 中的相同方法:

fun BigInteger.plus(other: BigInteger): BigInteger = this.add(other)
fun BigInteger.minus(other: BigInteger): BigInteger = this.subtract(other)

object Binomial {
    val map = mutableMapOf<Pair<BigInteger, BigInteger>, BigInteger>()

    fun binomial(n: Int, k: Int): BigInteger =
        binomial(Pair(n.toBigInteger(), k.toBigInteger()))


    fun binomial(x: Pair<BigInteger, BigInteger>): BigInteger {
        val (n, k) = x
        if (k == ZERO || n == k) {
            return ONE
        }
        return map.getOrPut(x) { binomial(Pair(n - ONE, k)) + binomial(Pair(n - ONE, k - ONE)) }
    }
}

fun main() {
    println(binomial(8, 4)) // 70
}

【讨论】:

    【解决方案2】:

    递归通常要慢得多,因为所有函数调用都必须存储在堆栈中以允许返回给调用者函数。在许多情况下,必须分配和复制内存以实现范围隔离。

    尝试这样的迭代算法:

     private static long binomial(int n, int k)
        {
            if (k>n-k)
                k=n-k;
    
            long b=1;
            for (int i=1, m=n; i<=k; i++, m--)
                b=b*m/i;
            return b;
        }
    

    【讨论】:

    • 我明白了,非常感谢您的解释。我假设我可以在计算非常大的数字时将 int 更改为 BigIntegers?
    • 真正的问题是隐式阶乘应该是memoized 以及其他值。
    • 我好像找不到m的声明。
    • 一个 int 也是如此。
    • 与我发布的算法相比,这个算法快得惊人。非常感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-11-21
    • 2021-12-27
    • 1970-01-01
    • 2010-09-07
    • 2021-01-24
    • 1970-01-01
    • 2018-02-22
    相关资源
    最近更新 更多