web-dev-qa-db-fra.com

Créer toutes les k combinaisons possibles de n éléments en C++

Il y a n personnes numérotées de 1 à n. Je dois écrire un code qui produit et imprime toutes les combinaisons différentes de k personnes à partir de ces n. S'il vous plaît expliquer l'algorithme utilisé pour cela. 

34
Prannoy Mittal

Je suppose que vous parlez de combinaisons dans le sens combinatoire (en d’autres termes, l’ordre des éléments n’a pas d’importance, donc [1 2 3] est identique à [2 1 3]). L’idée est donc assez simple, si vous comprenez induction/récursivité: pour obtenir toutes les combinaisons K- élément, vous devez d’abord sélectionner l’élément initial d’une combinaison parmi un ensemble de personnes existant, puis «concaténer» cet élément initial avec toutes les combinaisons possibles de K-1 personnes produites à partir d'éléments qui succèdent à l'élément initial.

Par exemple, supposons que nous voulions prendre toutes les combinaisons de 3 personnes sur un ensemble de 5 personnes. Ensuite, toutes les combinaisons possibles de 3 personnes peuvent être exprimées en termes de toutes les combinaisons possibles de 2 personnes:

comb({ 1 2 3 4 5 }, 3) =
{ 1, comb({ 2 3 4 5 }, 2) } and
{ 2, comb({ 3 4 5 }, 2) } and
{ 3, comb({ 4 5 }, 2) }

Voici le code C++ qui implémente cette idée:

#include <iostream>
#include <vector>

using namespace std;

vector<int> people;
vector<int> combination;

void pretty_print(const vector<int>& v) {
  static int count = 0;
  cout << "combination no " << (++count) << ": [ ";
  for (int i = 0; i < v.size(); ++i) { cout << v[i] << " "; }
  cout << "] " << endl;
}

void go(int offset, int k) {
  if (k == 0) {
    pretty_print(combination);
    return;
  }
  for (int i = offset; i <= people.size() - k; ++i) {
    combination.Push_back(people[i]);
    go(i+1, k-1);
    combination.pop_back();
  }
}

int main() {
  int n = 5, k = 3;

  for (int i = 0; i < n; ++i) { people.Push_back(i+1); }
  go(0, k);

  return 0;
}

Et voici la sortie pour N = 5, K = 3:

combination no 1:  [ 1 2 3 ] 
combination no 2:  [ 1 2 4 ] 
combination no 3:  [ 1 2 5 ] 
combination no 4:  [ 1 3 4 ] 
combination no 5:  [ 1 3 5 ] 
combination no 6:  [ 1 4 5 ] 
combination no 7:  [ 2 3 4 ] 
combination no 8:  [ 2 3 5 ] 
combination no 9:  [ 2 4 5 ] 
combination no 10: [ 3 4 5 ] 
52
dorserg

De code Rosetta

#include <algorithm>
#include <iostream>
#include <string>

void comb(int N, int K)
{
    std::string bitmask(K, 1); // K leading 1's
    bitmask.resize(N, 0); // N-K trailing 0's

    // print integers and permute bitmask
    do {
        for (int i = 0; i < N; ++i) // [0..N-1] integers
        {
            if (bitmask[i]) std::cout << " " << i;
        }
        std::cout << std::endl;
    } while (std::prev_permutation(bitmask.begin(), bitmask.end()));
}

int main()
{
    comb(5, 3);
}

sortie

 0 1 2
 0 1 3
 0 1 4
 0 2 3
 0 2 4
 0 3 4
 1 2 3
 1 2 4
 1 3 4
 2 3 4

Analyse et idée

Le but est de jouer avec la représentation binaire des nombres Par exemple, le nombre 7 en binaire est 0111.

Donc, cette représentation binaire peut aussi être vue comme une liste d'assignation en tant que telle:

Pour chaque bit i si le bit est défini (c.-à-d. 1) signifie que le i ème élément est affecté, sinon non.

Ensuite, en calculant simplement une liste de nombres binaires consécutifs et en exploitant la représentation binaire (qui peut être très rapide), on obtient un algorithme permettant de calculer toutes les combinaisons de N sur k.

Le tri à la fin (de certaines implémentations) est pas nécessaire. C’est un moyen de normaliser le résultat de façon déterministe, c’est-à-dire que pour les mêmes nombres (N, K) et le même algorithme, le même ordre des combinaisons est renvoyé.

Pour en savoir plus sur les représentations numériques et leurs relations avec les combinaisons, les permutations, les ensembles de puissance (et autres éléments intéressants), consultez Système de nombres combinatoire , Système de nombres factoriel

34
Nikos M.

En Python, cela est implémenté comme itertools.combinations

https://docs.python.org/2/library/itertools.html#itertools.combinations

En C++, une telle fonction de combinaison pourrait être implémentée sur la base de la fonction de permutation.

L'idée de base est d'utiliser un vecteur de taille n et de ne définir que k élément à 1, toutes les combinaisons de nchoosek pouvant être obtenues en collectant les k éléments de chaque permutation ..__ grand espace, car la combinaison est généralement un très grand nombre. Il vaut mieux être implémenté en tant que générateur ou mettre des codes de travail dans do_sth ().

Exemple de code:

#include <vector>
#include <iostream>
#include <iterator>
#include <algorithm>

using namespace std;

int main(void) {

  int n=5, k=3;

  // vector<vector<int> > combinations;
 vector<int> selected;
 vector<int> selector(n);
 fill(selector.begin(), selector.begin() + k, 1);
 do {
     for (int i = 0; i < n; i++) {
      if (selector[i]) {
            selected.Push_back(i);
      }
     }
     //     combinations.Push_back(selected);
         do_sth(selected);
     copy(selected.begin(), selected.end(), ostream_iterator<int>(cout, " "));
     cout << endl;
     selected.clear();
 }
 while (prev_permutation(selector.begin(), selector.end()));

  return 0;
}

et la sortie est

0 1 2 
0 1 3 
0 1 4 
0 2 3 
0 2 4 
0 3 4 
1 2 3 
1 2 4 
1 3 4 
2 3 4 

Cette solution est en fait une copie avec Génération de combinaisons en c ++

7
Ning

Si le numéro de l'ensemble est compris entre 32, 64 ou une taille primitive native de la machine, vous pouvez le faire avec une simple manipulation de bits.

template<typename T>
void combo(const T& c, int k)
{
    int n = c.size();
    int combo = (1 << k) - 1;       // k bit sets
    while (combo < 1<<n) {

        pretty_print(c, combo);

        int x = combo & -combo;
        int y = combo + x;
        int z = (combo & ~y);
        combo = z / x;
        combo >>= 1;
        combo |= y;
    }
}

cet exemple appelle la fonction pretty_print () par l'ordre du dictionnaire.

Par exemple. Vous voulez avoir 6C3 et en supposant que le 'combo' actuel est 010110 . Il est évident que le prochain combo DOIT être 011001 . 011001 est: 010000 | 001000 | 000001

010000: 1s continuellement supprimés du côté LSB ..__ 001000: régler 1 sur le suivant de 1s continuellement du côté LSB ..__ 000001: décalé continuellement de 1s du LSB vers la droite et supprimer le bit LSB.

int x = combo & -combo;

ceci obtient le plus bas 1.

int y = combo + x;

ceci élimine continuellement les 1s du côté LSB et met 1 sur le suivant (dans le cas ci-dessus, 010000 | 001000)

int z = (combo & ~y)

cela vous donne les 1 en continu du côté LSB (000110).

combo = z / x;
combo >> =1;

ceci est pour 'décalé continuellement de 1 s de LSB vers la droite et supprimer le bit de LSB'.

La tâche finale consiste donc à OR y à ce qui précède.

combo |= y;

Quelques exemples concrets simples:

#include <bits/stdc++.h>

using namespace std;

template<typename T>
void pretty_print(const T& c, int combo)
{
    int n = c.size();
    for (int i = 0; i < n; ++i) {
        if ((combo >> i) & 1)
            cout << c[i] << ' ';
    }
    cout << endl;
}

template<typename T>
void combo(const T& c, int k)
{
    int n = c.size();
    int combo = (1 << k) - 1;       // k bit sets
    while (combo < 1<<n) {

        pretty_print(c, combo);

        int x = combo & -combo;
        int y = combo + x;
        int z = (combo & ~y);
        combo = z / x;
        combo >>= 1;
        combo |= y;
    }
}

int main()
{
    vector<char> c0 = {'1', '2', '3', '4', '5'};
    combo(c0, 3);

    vector<char> c1 = {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
    combo(c1, 4);
    return 0;
}

résultat :

1 2 3 
1 2 4 
1 3 4 
2 3 4 
1 2 5 
1 3 5 
2 3 5 
1 4 5 
2 4 5 
3 4 5 
a b c d 
a b c e 
a b d e 
a c d e 
b c d e 
a b c f 
a b d f 
a c d f 
b c d f 
a b e f 
a c e f 
b c e f 
a d e f 
b d e f 
c d e f 
a b c g 
a b d g 
a c d g 
b c d g 
a b e g 
a c e g 
b c e g 
a d e g 
b d e g 
c d e g 
a b f g 
a c f g 
b c f g 
a d f g 
b d f g 
c d f g 
a e f g 
b e f g 
c e f g 
d e f g 
5
user7781254

J'ai écrit une classe en C # pour gérer des fonctions communes permettant de travailler avec le coefficient binomial, qui est le type de problème auquel appartient votre problème. Il effectue les tâches suivantes:

  1. Affiche tous les K-index au format Nice pour tout N, choisissez K dans un fichier. Les K-index peuvent être remplacés par des chaînes ou des lettres plus descriptives. Cette méthode rend la résolution de ce type de problème assez triviale.

  2. Convertit les K-index en index approprié d'une entrée de la table des coefficients binomiaux triée. Cette technique est beaucoup plus rapide que les anciennes techniques publiées qui reposent sur l'itération. Pour ce faire, il utilise une propriété mathématique inhérente au triangle de Pascal. Mon article en parle. Je crois que je suis le premier à découvrir et à publier cette technique.

  3. Convertit l'index d'une table de coefficients binomiale triée en index K correspondants. Je crois que c'est aussi plus rapide que les autres solutions.

  4. Usages Mark Dominus méthode de calcul du coefficient binomial, beaucoup moins susceptible de déborder et fonctionnant avec des nombres plus grands.

  5. La classe est écrite en .NET C # et permet de gérer les objets liés au problème (le cas échéant) à l'aide d'une liste générique. Le constructeur de cette classe prend une valeur bool appelée InitTable qui, lorsqu'elle est vraie, créera une liste générique destinée à contenir les objets à gérer. Si cette valeur est false, la table ne sera pas créée. La table n'a pas besoin d'être créée pour exécuter les 4 méthodes ci-dessus. Des méthodes d'accesseur sont fournies pour accéder à la table.

  6. Il existe une classe de test associée qui montre comment utiliser la classe et ses méthodes. Il a été testé de manière approfondie avec 2 cas et il n'y a aucun bogue connu.

Pour en savoir plus sur cette classe et télécharger le code, voir Tablizing The Binomial Coeffieicent .

Il devrait être assez simple de transférer la classe en C++.

La solution à votre problème consiste à générer les K-index pour chaque cas N select K. Par exemple:

int NumPeople = 10;
int N = TotalColumns;
// Loop thru all the possible groups of combinations.
for (int K = N - 1; K < N; K++)
{
   // Create the bin coeff object required to get all
   // the combos for this N choose K combination.
   BinCoeff<int> BC = new BinCoeff<int>(N, K, false);
   int NumCombos = BinCoeff<int>.GetBinCoeff(N, K);
   int[] KIndexes = new int[K];
   BC.OutputKIndexes(FileName, DispChars, "", " ", 60, false);
   // Loop thru all the combinations for this N choose K case.
   for (int Combo = 0; Combo < NumCombos; Combo++)
   {
      // Get the k-indexes for this combination, which in this case
      // are the indexes to each person in the problem set.
      BC.GetKIndexes(Loop, KIndexes);
      // Do whatever processing that needs to be done with the indicies in KIndexes.
      ...
   }
}

La méthode OutputKIndexes peut également être utilisée pour exporter les K-index dans un fichier, mais elle utilisera un fichier différent pour chaque N cas K choisi.

2
Bob Bryan

Voici un algorithme que j'ai conçu pour résoudre ce problème. Vous devriez pouvoir le modifier pour travailler avec votre code.

void r_nCr(const unsigned int &startNum, const unsigned int &bitVal, const unsigned int &testNum) // Should be called with arguments (2^r)-1, 2^(r-1), 2^(n-1)
{
    unsigned int n = (startNum - bitVal) << 1;
    n += bitVal ? 1 : 0;

    for (unsigned int i = log2(testNum) + 1; i > 0; i--) // Prints combination as a series of 1s and 0s
        cout << (n >> (i - 1) & 1);
    cout << endl;

    if (!(n & testNum) && n != startNum)
        r_nCr(n, bitVal, testNum);

    if (bitVal && bitVal < testNum)
        r_nCr(startNum, bitVal >> 1, testNum);
}

Vous pouvez voir une explication de la façon dont cela fonctionne ici .

2
android927

Cela peut aussi être fait en utilisant le retour arrière en maintenant un tableau visité.

void foo(vector<vector<int> > &s,vector<int> &data,int go,int k,vector<int> &vis,int tot)
{

    vis[go]=1;
    data.Push_back(go);
    if(data.size()==k)
    {
        s.Push_back(data);
        vis[go]=0;
    data.pop_back();
        return;
    }

    for(int i=go+1;i<=tot;++i)
    {
       if(!vis[i])
       {
           foo(s,data,i,k,vis,tot);
       }
    }
    vis[go]=0;
    data.pop_back();
}


vector<vector<int> > Solution::combine(int n, int k) {
   vector<int> data;
   vector<int> vis(n+1,0);
   vector<vector<int> > sol;
   for(int i=1;i<=n;++i)
   {
       for(int i=1;i<=n;++i) vis[i]=0;
   foo(sol,data,i,k,vis,n);
   }
   return sol;

}
0
Arpit Gupta

Derrière le lien ci-dessous se trouve une réponse générique C # à ce problème: Comment formater toutes les combinaisons à partir d’une liste d’objets. Vous pouvez facilement limiter les résultats à la longueur de k.

https://stackoverflow.com/a/40417765/2613458

0
jaolho