Combinatorio 'N scegliere R' in java matematica?
-
18-09-2019 - |
Domanda
C'è una costruito nel metodo in una libreria Java che può calcolare 'N scegliere R' per qualsiasi N, R?
Soluzione
L'apache-commons "Math" supporta questa in org.apache.commons.math4.util.CombinatoricsUtils
Altri suggerimenti
La Formula
In realtà è molto facile da calcolare N choose K
senza nemmeno calcolare fattoriali.
Sappiamo che la formula per (N choose K)
è:
N!
--------
(N-K)!K!
Quindi, la formula per (N choose K+1)
è:
N! N! N! N! (N-K)
---------------- = --------------- = -------------------- = -------- x -----
(N-(K+1))!(K+1)! (N-K-1)! (K+1)! (N-K)!/(N-K) K!(K+1) (N-K)!K! (K+1)
Cioè:
(N choose K+1) = (N choose K) * (N-K)/(K+1)
Sappiamo anche che (N choose 0)
è:
N!
---- = 1
N!0!
Quindi, questo ci dà un facile punto di partenza, e utilizzando la formula di cui sopra, possiamo trovare (N choose K)
per qualsiasi K > 0
con moltiplicazioni e divisioni K
K
.
Triangolo di Easy Pascal
Mettere quanto sopra insieme, possiamo facilmente generare triangolo di Pascal come segue:
for (int n = 0; n < 10; n++) {
int nCk = 1;
for (int k = 0; k <= n; k++) {
System.out.print(nCk + " ");
nCk = nCk * (n-k) / (k+1);
}
System.out.println();
}
Questo stampa:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1
Versione BigInteger
Applicando la formula per BigInteger
è semplice:
static BigInteger binomial(final int N, final int K) {
BigInteger ret = BigInteger.ONE;
for (int k = 0; k < K; k++) {
ret = ret.multiply(BigInteger.valueOf(N-k))
.divide(BigInteger.valueOf(k+1));
}
return ret;
}
//...
System.out.println(binomial(133, 71));
// prints "555687036928510235891585199545206017600"
Secondo Google, 133 scegliere 71 = 5,55,687037 millions × 10 38 .
Riferimenti
Il ricorsivo definizione ti dà una bella funzione semplice scegliere quale lavorerà bene per le piccole valori. Se avete intenzione di correre questo metodo molto, o su valori di grandi dimensioni, che avrebbe pagato per Memoize esso, ma per il resto funziona bene.
public static long choose(long total, long choose){
if(total < choose)
return 0;
if(choose == 0 || choose == total)
return 1;
return choose(total-1,choose-1)+choose(total-1,choose);
}
Migliorare il tempo di esecuzione di questa funzione è lasciato come esercizio per il lettore :)
Sto solo cercando di calcolare il numero di 2 combinazioni di carte con diverse dimensioni della piattaforma ...
Non c'è bisogno di importare una libreria esterna - dalla definizione di combinazioni, con schede n
che sarebbero n*(n-1)/2
Domanda bonus: Questa stessa formula calcola la somma dei primi numeri interi n-1
- Capite perché sono la stessa cosa? :)
La formula matematica per questo è:
N!/((R!)(N-R)!)
Non dovrebbe essere difficile capirlo da lì:)
N! / ((R!) (N-R)!)
C'è molto si può annullare in basso in questa formula, quindi di solito i fattoriali non sono un problema. Diciamo che R> (N-R) quindi annullare giù N! / R! a (R + 1) * (R + 2) * ... * N. Ma la vera, int è molto limitata (circa 13!).
Ma allora si potrebbe con ogni iterazione anche dividere. In pseudocodice:
d := 1
r := 1
m := max(R, N-R)+1
for (; m <= N; m++, d++ ) {
r *= m
r /= d
}
E 'importante iniziare la divisione con una sola, anche se questo sembra essere superfluo. Ma facciamo un esempio:
for N = 6, R = 2: 6!/(2!*4!) => 5*6/(1*2)
Se lasciamo 1 su avremmo calcolare 5/2 * 6. La divisione avrebbe lasciato il dominio intero. Lasciando 1 possiamo garantire che non facciamo che come primo o secondo operando della moltiplicazione è ancora.
Per la stessa ragione non usiamo r *= (m/d)
.
Il tutto potrebbe essere rivisto per
r := max(R, N-R)+1
for (m := r+1,d := 2; m <= N; m++, d++ ) {
r *= m
r /= d
}
Il seguente procedura calcolerà la n-scegliere-k, usando la definizione ricorsiva e Memoizzazione. La routine è estremamente rapida e precisa:
inline unsigned long long n_choose_k(const unsigned long long& n,
const unsigned long long& k)
{
if (n < k) return 0;
if (0 == n) return 0;
if (0 == k) return 1;
if (n == k) return 1;
if (1 == k) return n;
typedef unsigned long long value_type;
value_type* table = new value_type[static_cast<std::size_t>(n * n)];
std::fill_n(table,n * n,0);
class n_choose_k_impl
{
public:
n_choose_k_impl(value_type* table,const value_type& dimension)
: table_(table),
dimension_(dimension)
{}
inline value_type& lookup(const value_type& n, const value_type& k)
{
return table_[dimension_ * n + k];
}
inline value_type compute(const value_type& n, const value_type& k)
{
if ((0 == k) || (k == n))
return 1;
value_type v1 = lookup(n - 1,k - 1);
if (0 == v1)
v1 = lookup(n - 1,k - 1) = compute(n - 1,k - 1);
value_type v2 = lookup(n - 1,k);
if (0 == v2)
v2 = lookup(n - 1,k) = compute(n - 1,k);
return v1 + v2;
}
value_type* table_;
value_type dimension_;
};
value_type result = n_choose_k_impl(table,n).compute(n,k);
delete [] table;
return result;
}
ArithmeticUtils.factorial
è apparentemente deprecato ora. Si prega di provare CombinatoricsUtils.binomialCoefficientDouble(n,r)
simile alla versione guava, c'è una classe BigIntegerMath qui da Richard J. Mathar riferito come org.nevec.rjm, che è il pacchetto delle classi.
La loro attuazione prevede due firme per il metodo binomiale: int, int e BigInteger, BigInteger
.Utilizzo di una HashMap per migliorare @ soluzione s' dimo414:
private static Map<Integer, Map<Integer, Integer>> map = new HashMap<>();
private static int choose(int total, int choose){
if(total < choose)
return 0;
if(choose == 0 || choose == total)
return 1;
if (! (map.containsKey(total) && map.get(total).containsKey(choose))){
map.put(total, new HashMap<>());
map.get(total).put(choose, choose(total-1,choose-1)+choose(total-1,choose));
}
return map.get(total).get(choose);
}
public static void combinationNcK(List<String> inputList, String prefix, int chooseCount, List<String> resultList) {
if (chooseCount == 0)
resultList.add(prefix);
else {
for (int i = 0; i < inputList.size(); i++)
combinationNcK(inputList.subList(i + 1, inputList.size()), prefix + "," + inputList.get(i), chooseCount - 1, resultList);
// Finally print once all combinations are done
if(prefix.equalsIgnoreCase("")){
resultList.stream().map(str->str.substring(1)).forEach(System.out::println);
}
}
}
public static void main(String[] args) {
List<String> positions = Arrays.asList(new String[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12" });
List<String> resultList = new ArrayList<String>();
combinationNcK(positions, "", 3, resultList);
}
Come per la formula: n / ((n-k) * k!!) Se ci limitiamo a calcola numeratore e denominatore, molti calcoli sarà sprecato e, probabilmente, la gamma di "int", "galleggiare" o anche "BigInteger" possono riempire. Così, per superare questo scenario, siamo in grado di annullare le cose prima ancora moltiplicando i valori.
supponiamo n = 6, k = 3
che è => 5 * 6 * 4 * 3 * 2 * 1 / ((3 * 2) * (3 * 2))
supponiamo se moltiplichiamo il numeratore, il campo può riempire. Meglio opzione è quella di annullare fuori prima ancora moltiplicando i valori.
In questo caso -> se cancelliamo tutto ciò che stiamo appena lasciato con: (2 * 5 * 2)
moltiplicano questi valori sono molto più facili e richiederanno meno calcolo.
=============================================== =======
Il codice di seguito citato funzionerà "efficiente" per i numeri in cui la:
- n == k
- k
- k == 0
- la differenza tra N e K è troppo grande, per esempio. n = 1000, k = 2
- k = n / 2 (PIÙ DURI)
- valore di k è quasi la metà di il valore di n
Probabilmente il codice può essere ancora migliorata.
BigInteger calculateCombination(int num, int k) {
if (num == k || k == 0)
return BigInteger.ONE ;
int numMinusK = num - k;
int stopAt; // if n=100, k=2 , can stop the multiplication process at 100*99
int denominator;
// if n=100, k=98 OR n=100, k=2 --> output remains same.
// thus choosing the smaller number to multiply with
if (numMinusK > k) {
stopAt = numMinusK;
denominator = k;
} else {
stopAt = k;
denominator = numMinusK;
}
// adding all the denominator nums into list
List<Integer> denoFactList = new ArrayList<Integer>();
for (int i = 2; i <= denominator; i++) {
denoFactList.add(i);
}
// creating multiples list, because 42 / 27 is not possible
// but 42 / 3 and followed by 42 / 2 is also possible
// leaving us only with "7"
List<Integer> multiplesList = breakInMultiples(denoFactList);
Collections.sort(multiplesList, Collections.reverseOrder());
Iterator<Integer> itr;
BigInteger total = BigInteger.ONE;
while (num > 0 && num > stopAt) {
long numToMultiplyWith = num;
if (!multiplesList.isEmpty()) {
itr = multiplesList.iterator();
while (itr.hasNext()) {
int val = itr.next();
if (numToMultiplyWith % val == 0) {
numToMultiplyWith = numToMultiplyWith / val;
itr.remove();
}
}
}
total = total.multiply(BigInteger.valueOf(numToMultiplyWith));
num--;
}
return total;
}
ArrayList<Integer> breakInMultiples(List<Integer> denoFactList) {
ArrayList<Integer> multiplesList = new ArrayList<>();
for (int i : denoFactList)
updateListWithMultiplesOf(multiplesList, i);
return multiplesList;
}
void updateListWithMultiplesOf(ArrayList<Integer> list, int i) {
int count = 2;
while (i > 1) {
while (i % count == 0) {
list.add(count);
i = i / count;
}
count++;
}
}
Ci sono già un sacco di soluzioni presentate.
-
Alcuni soluzione non ha ritenuto integer overflow.
-
Alcuni soluzione calcolato tutti i possibili nCr mentre dati n e r. Il risultato è più tempo e lo spazio necessari.
Nella maggior parte dei casi abbiamo bisogno di calcolare direttamente NCR. Ho intenzione di condividere una soluzione in più.
static long gcd(long a, long b) {
if (a == 0) return b;
return gcd(b%a, a);
}
// Compute (a^n) % m
static long bigMod(long a, long n, long m) {
if (n == 0) return 1;
if (n == 1) return a % m;
long ret = bigMod(a, n/2, m);
ret = (ret * ret) % m;
if (n % 2 == 1) return (ret * a) % m;
return ret;
}
// Function to find (1/a mod m).
// This function can find mod inverse if m are prime
static long modInverseFarmetsTheorem(long a, long m) {
if (gcd(a, m) != 1) return -1;
return bigMod(a, m-2, m);
}
// This function finds ncr using modular multiplicative inverse
static long ncr(long n, long r, long m) {
if (n == r) return 1;
if (r == 1) return n;
long start = n - Math.max(r, n - r) + 1;
long ret = 1;
for (long i = start; i <= n; i++) ret = (ret * i) % m;
long until = Math.min(r, n - r), denom = 1;
for (long i = 1; i <= until; i++) denom = (denom * i) % m;
ret = (ret * modInverseFarmetsTheorem(denom, m)) % m;
return ret;
}
Invece di attuazione n scegliere k in modo ricorsivo (che può diventare lento con grandi numeri), possiamo anche fare uso del fatto che:
n(n-1)(n-2)...(n-k+1)
n choose k = --------------------
k!
Abbiamo ancora bisogno di calcolare k !, ma questo può essere fatto molto più veloce rispetto al metodo ricorsivo.
private static long choose(long n, long k) {
long numerator = 1;
long denominator = 1;
for (long i = n; i >= (n - k + 1); i--) {
numerator *= i;
}
for (long i = k; i >= 1; i--) {
denominator *= i;
}
return (numerator / denominator);
}
Si noti che il metodo di scelta di cui sopra presuppone che né n né k è negativo. Inoltre, il tipo di dati a lungo può traboccare per grandi valori abbastanza. Una versione BigInteger deve essere utilizzato se il resp risultato. numeratore e / o denominatore si prevede superiore a 64 bit.
public static long nCr(int n, int r) {
long a = n;
long b = r;
long c = (n - r);
for (int o = (int)a - 1; o > 0; o--) { a = a * o; }
for (int o = (int)b - 1; o > 0; o--) { b = b * o; }
for (int o = (int)c - 1; o > 0; o--) { c = c * o; }
return (a / (b * c)); // n! / r! * (n - r)!
}
A cura dalla risposta che ho fatto qualche anno fa, dove a, b, ec erano int e integer overflow reso il metodo critico inutilizzabile. Questo non è davvero niente di meglio in termini di affidabilità, ma è pigro.
Questo mattone volontà pure, se il valore va oltre il limite di tempo ... Non molto fattibile a meno che non si sta cercando di trovare qualche soluzione rapida per un progetto scolastico o qualcosa del genere.