Java で選択表記を実装する良い方法は何ですか?
-
16-09-2019 - |
質問
...できれば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;
}
これを行うより良い方法があるかどうか疑問に思っています。
解決
choose(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;
他のヒント
あなたが扱っている数字は非常に大きくなり、すぐにあなたに予期せず誤った結果を与え、double
値の精度を超えてしまいます。このような理由から、あなたがこの問題に苦しむしないようjava.math.BigInteger
を使用するなど、任意精度のソリューションを、検討する必要があります。
正直に言うと、あなたが得たものは私には非常に明確に見えます。確かに、私は return ステートメントを中かっこで囲みます。それが私が従う慣習だからです。しかし、それを除けば、見た目はこれ以上ないほど良くなります。
おそらく 2 番目のループの順序を逆にして、両方のループが昇順になるようにすると思います。
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;
}
これは 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!/((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;
このバージョンはBigInteger
または浮動小数点演算を必要とし、28を超える62 62がオーバーフローを生じさせるための第一の対よりも少ない全n
のオーバーフローエラーなしで動作していません。
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;
}