Domanda

C'è una costruito nel metodo in una libreria Java che può calcolare 'N scegliere R' per qualsiasi N, R?

È stato utile?

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:

  1. n == k
  2. k
  3. k == 0
  4. la differenza tra N e K è troppo grande, per esempio. n = 1000, k = 2
  5. k = n / 2 (PIÙ DURI)
  6. 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.

  1. Alcuni soluzione non ha ritenuto integer overflow.

  2. 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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top