【问题标题】:What is a good way to implement choose notation in Java?在 Java 中实现选择符号的好方法是什么?
【发布时间】:2009-11-05 06:27:01
【问题描述】:

... 最好是在 Java 中。这是我所拥有的:

//x choose y
public static double choose(int x, int y) {
    if (y < 0 || y > x) return 0;
    if (y == 0 || y == x) return 1;

    double answer = 1;
    for (int i = x-y+1; i <= x; i++) {
        answer = answer * i;
    }
    for (int j = y; j > 1; j--) {
        answer = answer / j;
    }
    return answer;
}

我想知道是否有更好的方法来做到这一点?

【问题讨论】:

  • 由于选择函数被定义为自然数上的二进制函数 (|N x |N => |N),因此在任何时候都不应使用双精度数。这实际上会使您选择的解决方案不必要地处于次优状态。

标签: java combinations binomial-coefficients


【解决方案1】:
选择(n,k)= n! / (n-k)! k!

你可以在 O(k) 中做这样的事情:

public static double choose(int x, int y) {
    if (y < 0 || y > x) return 0;
    if (y > x/2) {
        // choose(n,k) == choose(n,n-k), 
        // so this could save a little effort
        y = x - y;
    }

    double denominator = 1.0, numerator = 1.0;
    for (int i = 1; i <= y; i++) {
        denominator *= i;
        numerator *= (x + 1 - i);
    }
    return numerator / denominator;
}

编辑如果x 和y 很大,如果你将答案分开,你会溢出得更慢(即,对于较大的x & y 值是安全的):

    double answer = 1.0;
    for (int i = 1; i <= y; i++) {
        answer *= (x + 1 - i);
        answer /= i;           // humor 280z80
    }
    return answer;

【讨论】:

  • -1:答案不必要地失去精度。 choose(9,4) 得到乘以 7.0/3 的乘法,这不能用双精度表示。请参阅我的正确实现。
  • 真的吗?正确的实现是将两个 long 强制转换为 BigInteger 对象,调用 BigInteger.GreatestCommonDivisor,然后将结果强制返回到 long?认真的吗?
  • 首先让你的返回正确的结果,然后我们可以谈谈哪个更有效。
  • @280Z28 不要太自负。 choose(11,5) =&gt; 461.99999999999994 使用这个算法,choose(11,5) =&gt; 2184 使用你的算法。但我得到错误答案的速度快了 10 倍。
  • 你为什么使用浮点数?在编辑后的答案中,变量 answer 绝不是浮点值。此外,如果您将分子从小到大循环,则溢出的可能性会更小。
【解决方案2】:

您正在处理的数字会变得非常大,并且会很快超过double 值的精度,从而给您带来意想不到的错误结果。出于这个原因,您可能需要考虑一个任意精度的解决方案,例如使用java.math.BigInteger,它不会遇到这个问题。

【讨论】:

  • 我认为可能同时运行两个循环(第二个循环需要朝相反的方向运行)。
【解决方案3】:

老实说,在我看来,你所拥有的一切都很清楚。诚然,我会将 return 语句放在大括号中,因为这是我遵循的约定,但除此之外,它看起来和它得到的一样好。

我想我可能会颠倒第二个循环的顺序,这样两个循环都是升序的。

正如 Greg 所说,如果您需要获得大数的准确答案,您应该考虑替代数据类型。鉴于结果应始终为整数,您可能希望选择BigInteger(尽管进行了所有除法,但结果始终为整数):

public static BigInteger choose(int x, int y) {
    if (y < 0 || y > x) 
       return BigInteger.ZERO;
    if (y == 0 || y == x) 
       return BigInteger.ONE;

    BigInteger answer = BigInteger.ONE;
    for (int i = x - y + 1; i <= x; i++) {
        answer = answer.multiply(BigInteger.valueOf(i));
    }
    for (int j = 1; j <= y; j++) {
        answer = answer.divide(BigInteger.valueOf(j));
    }
    return answer;
}

【讨论】:

    【解决方案4】:

    我用 C# 编写了这个代码,但我试图让它尽可能适用于 Java。

    来自其中一些来源,以及我的一些小东西。

    代码:

    public static long BinomialCoefficient(long n, long k)
    {
        if (n / 2 < k)
            return BinomialCoefficient(n, n - k);
    
        if (k > n)
            return 0;
    
        if (k == 0)
            return 1;
    
        long result = n;
        for (long d = 2; d <= k; d++)
        {
            long gcd = (long)BigInteger.GreatestCommonDivisor(d, n);
            result *= (n / gcd);
            result /= (d / gcd);
            n++;
        }
    
        return result;
    }
    

    【讨论】:

    • 这段代码很奇怪。为什么n不断增加?为什么不从 d 派生 n 而不是在循环中修改它呢?
    【解决方案5】:

    为

    N!/((R!)(N-R)!)

    使用这个(伪代码)

    if (R>N) return 0;
    
    long r = max(R, N-r)+1;
    if (R==N) return 1;
    
    for (long m = r+1, long d = 2; m <= N; m++, d++ ) {
        r *= m;
        r /= d;
    }
    return r;
    

    【讨论】:

      【解决方案6】:

      此版本不需要 BigInteger 或浮点运算,并且对于所有小于 62 的 n 都不会出现溢出错误。62 over 28 是导致溢出的第一对。

      public static long nChooseK(int n, int k) {
          k = Math.min(k, n - k);
      
          if (n < 0 || k < 0)
              throw new IllegalArgumentException();
      
          if (k == 0)
              return 1;
      
          long value = n--;
      
          for (int i = 2; i <= k; i++) {
              value = Math.multiplyExact(value, n--);
              value /= i;
          }
      
          return value;
      }
      

      下面的测试证明这是真的:

      @Test
      void nChooseKLongVsBigInt() {
          for (int n = 0; n < 62; n++) {
              for (int k = 0; k <= n; k++) {
                  assertEquals(nChooseKBigInt(n, k), BigInteger.valueOf(nChooseK(n, k)));
              }
          }
      }
      
      private BigInteger nChooseKBigInt(int n, int k) {
          return factorial(n).divide(factorial(k).multiply(factorial(n - k)));
      }
      
      private BigInteger factorial(int number) {
          BigInteger result = BigInteger.ONE;
      
          for (int factor = 2; factor <= number; factor++) {
              result = result.multiply(BigInteger.valueOf(factor));
          }
      
          return result;
      }
      

      【讨论】:

        猜你喜欢
        • 2012-01-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多