2
votes

Le coefficient binomial C ++ est trop lent

J'ai essayé de calculer le coefficient binomial en faisant une récursion avec le triangle de Pascal. Cela fonctionne très bien pour les petits nombres, mais 20 plus est soit très lent, soit ne fonctionne pas du tout.

J'ai essayé de rechercher des techniques d'optimisation, telles que le "chaching", mais elles ne semblent pas vraiment bien intégré en C ++.

Voici le code si cela vous aide.

int binom(const int n, const int k)
{
    double sum;

    if(n == 0 || k == 0){
            sum = 1;
    }
    else{
    sum = binom(n-1,k-1)+binom(n-1,k);
    }

    if((n== 1 && k== 0) || (n== 1 && k== 1))
       {
           sum = 1;
       }
    if(k > n)
    {
        sum = 0;
    }

    return sum;

}

int main()
{
int n;
int k;
int sum;

cout << "Enter a n: ";
cin >> n;
cout << "Enter a k: ";
cin >> k;

Summe = binom(n,k);

cout << endl << endl << "Number of possible combinations: " << sum << 
endl;

}

Je suppose que le programme perd beaucoup de temps à calculer les résultats qu'il a déjà calculé. Il doit en quelque sorte mémoriser les résultats passés.


8 commentaires

Veuillez donner un exemple d'entrée d'échantillon, de sortie attendue et de sortie réelle.


votre estimation est correcte et c'est exactement ce que la mise en cache essaie d'éviter. Qu'avez-vous essayé de mettre en œuvre? si vous n’avez pas la moindre idée, comment pourriez-vous penser à stocker une valeur de récupération entre les appels successifs?


vous pouvez mettre vous-même un échantillon. Essayez 50 pour n et 45 pour k par exemple.


Considérez que cet algorithme ajoute essentiellement un groupe de 1, donc il fonctionne au moins aussi longtemps que l'ampleur du résultat .. pas vraiment idéal pour les fonctions avec des résultats potentiellement importants


La sortie attendue pour cela devrait être 2118760, mais je tue toujours le programme car il ne se termine jamais


@Christophe je n'ai pas essayé car cela semble trop compliqué. Dans Sage, il vous suffit de mettre "cached_function" devant lui. Cela semble plus compliqué en C ++


@harold ne sonne pas bien, ouais.


Voici quelques alternatives, la plupart sans mémorisation mais utilisant simplement de meilleurs algorithmes: stackoverflow.com/q/9330915/555045


3 Réponses :


1
votes

Vous calculez plusieurs fois des valeurs binomiales. Une solution rapide est la mémorisation.

Non testé:

int binom(int n, int k);

int binom_mem(int n, int k)
{
    static std::map<std::pair<int, int>, std::optional<int>> lookup_table;
    auto const input = std::pair{n,k};
    if (lookup_table[input].has_value() == false) {
        lookup_table[input] = binom(n, k);
    }
    return lookup_table[input];
}

int binom(int n, int k)
{
    double sum;

    if (n == 0 || k == 0){
        sum = 1;
    } else {
        sum = binom_mem(n-1,k-1) + binom_mem(n-1,k);
    }

    if ((n== 1 && k== 0) || (n== 1 && k== 1))
    {
        sum = 1;
    }
    if(k > n)
    {
        sum = 0;
    }

    return sum;
}

Une meilleure solution serait de tourner la récursion tailrec (pas facile avec les doubles récursions) ou mieux encore, de ne pas utiliser la récursivité du tout;)


2 commentaires

Pour ajouter à la réponse, je suppose qu'il existe de meilleures façons plus simples de calculer le coefficient binomial sans passer par la récursivité.


@jeffrycopps c'est vrai -> ajouté à A.



1
votes

Je mettrais en cache les résultats de chaque calcul dans une carte. Vous ne pouvez pas créer une carte avec une clé complexe, mais vous pouvez transformer la clé en chaîne.

if(k > n)
{
    return 0;
}

Ensuite, ayez une carte globale:

map<string, double> cachedValues;


3 commentaires

Je n'ai jamais essayé votre solution. Ma sérialisation fonctionne. Pour tout problème particulier, si vous demandez à 4 programmeurs, vous obtiendrez 6 à 15 solutions différentes. Je parie que ma sérialisation est plus facile à comprendre pour un nouvel étudiant en programmation.


Je ne voulais pas paraître méchant, je suis désolé. C'était juste une plaisanterie de réaction à une solution drôle / sous-optimale. J'ai retiré mon commentaire.


Non, c'est cool. Cela m'a amené à regarder votre exemple. Je suis en fait assez récemment revenu au C ++, ayant programmé Java pendant 20 ans, et beaucoup de choses ont changé. Je m'habitue toujours à certains d'entre eux, comme ce que vous avez fait pour la clé de votre carte.



3
votes

Je suppose que le programme perd beaucoup de temps à calculer les résultats qu'il a déjà calculés.

C'est certainement vrai.

À ce sujet, je vous suggère de jeter un œil à Sujet de programmation dynamique .

Il existe une classe de problèmes qui nécessite une complexité d'exécution exponentielle, mais ils peuvent être résolus avec des techniques de programmation dynamique . Cela réduirait la complexité d'exécution à une complexité polynomiale (la plupart du temps, au détriment de la complexité croissante de l'espace).


Les approches courantes de la programmation dynamique sont:

  • Top-Down (exploitant la mémorisation et la récursivité).
  • De bas en haut (itératif).

Ci-dessous, ma solution bottom-up (rapide et compacte):

(n, k) = (n - 1, k - 1) * n / k

Cet algorithme a une complexité d'exécution O (k) et complexité spatiale O (k) . En effet, c'est une solution linéaire.

De plus, cette solution est plus simple et plus rapide que l'approche récursive. Il est très compatible avec le cache du processeur .

Notez également qu'il n'y a pas de dépendance sur n.

J'ai atteint ce résultat exploitation d'opérations mathématiques simples et obtention de la formule suivante:

int BinomialCoefficient(const int n, const int k) {
  std::vector<int> aSolutions(k);
  aSolutions[0] = n - k + 1;

  for (int i = 1; i < k; ++i) {
    aSolutions[i] = aSolutions[i - 1] * (n - k + 1 + i) / (i + 1);
  }

  return aSolutions[k - 1];
}

Quelques références mathématiques sur le coefficient binomial .


Remarque

L'algorithme n'a pas vraiment besoin d'une complexité spatiale de O (k) . En effet, la solution à la i-ème étape ne dépend que de (i-1) -th . Par conséquent, il n'est pas nécessaire de stocker toutes les solutions intermédiaires, mais uniquement celle de l'étape précédente. Cela rendrait l'algorithme O (1) en termes de complexité spatiale.

Cependant, je préférerais garder toutes les solutions intermédiaires dans le code de la solution pour mieux montrer le principe derrière le Méthodologie de programmation dynamique .

Voici mon référentiel avec l'algorithme optimisé .


2 commentaires

Je pense que c'est une meilleure réponse que de simplement présenter un programme pour résoudre le problème, car cela dit quelque chose sur les concepts sous-jacents qui peuvent aider OP dans d'autres situations.


Explication parfaite, suggestions supplémentaires et une explication des mathématiques derrière. Tout simplement génial, merci!