Calculer les nombres de catalan en C++ et Python

26 Apr 2022 26 Apr 2022 6698 vues ESSADDOUKI Mostafa 6 min de lecture
Définition Les nombres de Catalan forment une suite d'entiers naturels très importante en combinatoire. Ils interviennent dans de nombreux problèmes de dénombrement : comptage de certains arbres binaires, parenthésages corrects, triangulations de polygones, chemins de réseau et autres structures discrètes.

On note cette suite C0, C1, C2, ..., Cn, .... Le n-ième nombre de Catalan est donné par la formule fermée suivante :

Formule fermée $$ C_n = \frac{1}{n+1}\binom{2n}{n} \qquad (n \ge 0) $$

Les premières valeurs sont :

C0 = 1
C1 = 1
C2 = 2
C3 = 5
C4 = 14
C5 = 42
À retenir Les nombres de Catalan peuvent être calculés de plusieurs manières : par définition récursive, par programmation dynamique, ou encore à l’aide de la formule fermée basée sur le coefficient binomial.

Problème

Écrire un programme qui lit un entier n et affiche la valeur du nombre de Catalan Cn.

Contraintes
  • 0 ≤ n
  • On suppose que le résultat tient dans le type entier utilisé

Entrée

L’entrée contient un entier n.

Sortie

Afficher la valeur du nombre de Catalan Cn.

Exemples

Exemple 1
Entrée
10
Sortie
16796
Explication : le 10e nombre de Catalan vaut 16796.
Exemple 2
Entrée
8
Sortie
1430
Explication : le 8e nombre de Catalan vaut 1430.
Exemple 3
Entrée
25
Sortie
4861946401452
Explication : le 25e nombre de Catalan vaut 4861946401452.

Relation de récurrence

Les nombres de Catalan vérifient également une relation de récurrence fondamentale :


Récurrence Math
C0 = 1

Pour n ≥ 0 :
C(n+1) = C0·Cn + C1·C(n-1) + ... + Cn·C0
       = Σ[k=0..n] Ck · C(n-k)

Cette relation exprime chaque terme à partir des termes précédents. Elle est particulièrement utile pour construire une solution récursive ou une solution par programmation dynamique.

 Observation

Pourquoi plusieurs méthodes ?

 Niveau : Débutant

Une solution récursive directe est simple à écrire, mais elle recalcule plusieurs fois les mêmes valeurs. Pour améliorer l’efficacité, on peut mémoriser les résultats intermédiaires avec la programmation dynamique, ou utiliser directement la formule fermée.

Solution 1 : approche récursive

On applique directement la relation de récurrence. Le cas de base est C0 = 1 et C1 = 1. Pour n > 1, on somme tous les produits :

$$ C_n = \sum_{i=0}^{n-1} C_i \cdot C_{n-1-i} $$

Attention Cette méthode est facile à comprendre mais peu efficace, car elle recalcule plusieurs fois les mêmes sous-problèmes. Sa complexité temporelle est exponentielle.
#include <iostream>
using namespace std;

unsigned long long catalan(int n) {
    if (n <= 1)
        return 1ULL;

    unsigned long long val = 0;
    for (int i = 0; i < n; i++)
        val += catalan(i) * catalan(n - 1 - i);

    return val;
}

int main() {
    int n;
    cin >> n;
    cout << catalan(n);
    return 0;
}
def catalan(n):
    if n <= 1:
        return 1

    val = 0
    for i in range(n):
        val += catalan(i) * catalan(n - 1 - i)

    return val

n = int(input())
print(catalan(n))
Complexité La complexité temporelle de cette approche est exponentielle. Elle devient vite impraticable lorsque n grandit.

Solution 2 : programmation dynamique

Dans la version récursive, les mêmes valeurs de Ck sont recalculées plusieurs fois. L’idée de la programmation dynamique consiste à stocker les valeurs déjà calculées dans un tableau puis à construire progressivement les termes de C0 jusqu’à Cn.

Principe On crée un tableau dp tel que dp[i] contient la valeur de Ci. Ensuite, pour chaque i, on calcule : $$ dp[i] = \sum_{j=0}^{i-1} dp[j] \times dp[i-1-j] $$
#include <iostream>
#include <vector>
using namespace std;

unsigned long long catalan(int n) {
    vector<unsigned long long> dp(n + 1, 0);

    dp[0] = 1;
    if (n >= 1)
        dp[1] = 1;

    for (int i = 2; i <= n; i++) {
        for (int j = 0; j < i; j++) {
            dp[i] += dp[j] * dp[i - 1 - j];
        }
    }

    return dp[n];
}

int main() {
    int n;
    cin >> n;
    cout << catalan(n);
    return 0;
}
def catalan(n):
    dp = [0] * (n + 1)

    dp[0] = 1
    if n >= 1:
        dp[1] = 1

    for i in range(2, n + 1):
        for j in range(i):
            dp[i] += dp[j] * dp[i - 1 - j]

    return dp[n]

n = int(input())
print(catalan(n))
Complexité temporelleComplexité spatialeIdée principale
O(n²)O(n)Éviter les recalculs en mémorisant les résultats

Solution 3 : formule fermée avec le coefficient binomial

Lorsque l’on souhaite une méthode plus rapide, on peut utiliser directement la formule :

Formule de Catalan $$ C_n = \frac{1}{n+1}\binom{2n}{n} $$

Il suffit donc de calculer efficacement le coefficient binomial \(\binom{2n}{n}\), puis de diviser le résultat par n+1.

Règle d'or Cette approche est généralement la plus efficace parmi les trois proposées ici : elle s’exécute en O(n) et n’utilise qu’un espace constant.
#include <iostream>
using namespace std;

unsigned long long coef_binomial(int n, int k) {
    if (k > n - k)
        k = n - k;

    unsigned long long coef = 1;
    for (int i = 0; i < k; i++) {
        coef *= (n - i);
        coef /= (i + 1);
    }

    return coef;
}

unsigned long long catalan(int n) {
    unsigned long long c = coef_binomial(2 * n, n);
    return c / (n + 1);
}

int main() {
    int n;
    cin >> n;
    cout << catalan(n);
    return 0;
}
def coef_binomial(n, k):
    if k > n - k:
        k = n - k

    coef = 1
    for i in range(k):
        coef = coef * (n - i)
        coef = coef // (i + 1)

    return coef

def catalan(n):
    c = coef_binomial(2 * n, n)
    return c // (n + 1)

n = int(input())
print(catalan(n))
Complexité temporelleComplexité spatialeAvantage
O(n)O(1)Méthode la plus rapide parmi celles présentées

Comparaison des trois approches

MéthodeComplexité temporelleComplexité spatialeRemarque
RécursiveExponentielleDépend de la profondeur d’appelSimple mais inefficace
Programmation dynamiqueO(n²)O(n)Bon compromis entre clarté et performance
Formule binomialeO(n)O(1)La plus efficace ici
Sortie
// La sortie apparaîtra ici…
Prêt · Ctrl+Entrée pour exécuter

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.