web-dev-qa-db-fra.com

Combinatoric 'N choisit R' en mathématiques Java?

Existe-t-il une méthode intégrée dans une bibliothèque Java permettant de calculer 'N choisit R' pour tout N, R?

54
Aly

Apache-commons "Math" le supporte in org.Apache.commons.math4.util.CombinatoricsUtils

44
theomega

La formule

Il est en fait très facile de calculer N choose K sans même calculer les factorielles.

Nous savons que la formule pour (N choose K) est la suivante:

    N!
 --------
 (N-K)!K!

Par conséquent, la formule pour (N choose K+1) est la suivante:

       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)

C'est:

(N choose K+1) = (N choose K) * (N-K)/(K+1)

Nous savons aussi que (N choose 0) est:

 N!
---- = 1
N!0!

Donc, cela nous donne un point de départ facile, et en utilisant la formule ci-dessus, nous pouvons trouver (N choose K) pour tout K > 0 avec des multiplications K et des divisions K.


Triangle Easy Pascal

En réunissant ce qui précède, nous pouvons facilement générer le triangle de Pascal comme suit:

    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();
    }

Cela imprime:

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 

BigInteger version

Appliquer la formule pour BigInteger est simple:

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"

Selon Google, 133 choisissez 71 = 5.55687037 × 1038 .


Références

93

La définition récursive vous donne une fonction de sélection assez simple qui fonctionnera très bien pour les petites valeurs. Si vous envisagez d'utiliser cette méthode de manière intensive ou sur des valeurs importantes, mémoriser la mémoire serait un avantage, mais cela fonctionne parfaitement.

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);
}

L’amélioration de l’exécution de cette fonction est laissée sous la forme d’un exercice pour le lecteur :)

19
dimo414

J'essaie juste de calculer le nombre de combinaisons de 2 cartes avec différentes tailles de deck ...

Pas besoin d'importer une bibliothèque externe - de la définition de la combinaison, avec des cartes n qui seraient n*(n-1)/2

Question bonus: Cette même formule calcule la somme des premiers nombres entiers n-1 - voyez-vous pourquoi ils sont identiques? :)

La formule mathématique pour ceci est:

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

Cela ne devrait pas être difficile à comprendre à partir de là :)

4
Aistina

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

Il y a beaucoup de choses que vous pouvez annuler avec cette formule, donc les factorielles ne posent généralement pas de problème. Disons que R> (N-R) annule ensuite N!/R! to (R + 1) * (R + 2) * ... * N. Mais vrai, int est très limité (environ 13!).

Mais alors on pourrait aussi diviser à chaque itération. En pseudocode:

d := 1
r := 1

m := max(R, N-R)+1
for (; m <= N; m++, d++ ) {
    r *= m
    r /= d
}

Il est important de commencer la division par un seul, même si cela semble superflu. Mais prenons un exemple:

for N = 6, R = 2: 6!/(2!*4!) => 5*6/(1*2)

Si nous laissons 1 sur, nous calculerons 5/2 * 6. La division quitterait le domaine entier. En laissant 1 dans nous garantissons que nous ne le ferons pas car le premier ou le second opérande de la multiplication est pair.

Pour la même raison, nous n'utilisons pas r *= (m/d).

Le tout pourrait être révisé pour

r := max(R, N-R)+1
for (m := r+1,d := 2; m <= N; m++, d++ ) {
    r *= m
    r /= d
}
4
3
Valentin Rocher

La routine suivante calculera le n-chois-k, en utilisant la définition récursive et la mémorisation La routine est extrêmement rapide et précise:

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;
}
2
Matthieu N.
1
Olivier Cailloux

ArithmeticUtils.factorial est apparemment obsolète maintenant. S'il vous plaît essayer CombinatoricsUtils.binomialCoefficientDouble(n,r)

1

Au lieu de mettre en œuvre n choisissez k de manière récursive (ce qui peut être lent avec de grands nombres), nous pouvons également utiliser le fait que:

                n(n-1)(n-2)...(n-k+1)
  n choose k =  --------------------
                        k!

Nous avons encore besoin de calculer k !, mais cela peut être fait beaucoup plus rapidement que la méthode récursive.

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);
}

Sachez que la méthode de sélection ci-dessus suppose que ni n ni k ne sont négatifs. En outre, le type de données long peut déborder pour des valeurs suffisamment grandes. Une version BigInteger doit être utilisée si le résultat resp. le numérateur et/ou le dénominateur devraient dépasser 64 bits.

0
Josh

Il y a déjà beaucoup de solutions soumises. 

  1. Certaines solutions ne prenaient pas en compte le dépassement d'entier.

  2. Une solution a calculé tous les nCr possibles en donnant n et r . Il en résulte plus de temps et d’espace.

Dans la plupart des cas, nous devons calculer directement nCr. Je vais partager une solution de plus.

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;
}
0
mahbub.kuet
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);
}
0
Ashwin Rayaprolu

Selon la formule: n!/((Nk)! * K!) Si nous calculons simplement le numérateur et le dénominateur, de nombreux calculs seront perdus et probablement la plage de "int", "float" ou même "BigInteger" peut remplir ..__ Ainsi, pour surmonter ce scénario, nous pouvons annuler les choses avant même de multiplier les valeurs.

supposons que n = 6, k = 3

qui est => 6 * 5 * 4 * 3 * 2 * 1/((3 * 2) * (3 * 2))

supposons que si on multiplie le numérateur, la plage peut se remplir. La meilleure option consiste à l’annuler avant même de multiplier les valeurs.

Dans ce cas -> Si nous annulons tout ce qui nous reste il nous reste: (2 * 5 * 2)

multiplier ces valeurs est beaucoup plus facile et nécessitera moins de calcul.

=============================================== ====

Le code mentionné ci-dessous fonctionnera "efficacement" pour les nombres où:

  1. n == k 
  2. k <n 
  3. k == 0
  4. la différence entre n et k est trop énorme, par exemple . n = 1000, k = 2
  5. k = n/2 (LE PLUS DIFFICILE) 
  6. La valeur de k est proche de la moitié de La valeur de n

Le code peut probablement encore être amélioré.

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++;
    }
}
0
Ravi Soni

Utiliser une hashmap pour améliorer la solution de @ 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);
}
0
Tao Zhang

Semblable à la version goyave, il existe une classe BigIntegerMath here de Richard J. Mathar appelée org.nevec.rjm, qui est le package des classes. 

Leur implémentation fournit deux signatures pour la méthode binomiale: int, int et BigInteger, BigInteger.

0
demongolem