Ci-dessous, les différences entre deux révisions de la page.
Les deux révisions précédentes Révision précédente Prochaine révision | Révision précédente Dernière révision Les deux révisions suivantes | ||
combinaisons [2010/01/09 08:47] tyrtamos |
combinaisons [2010/01/10 09:23] tyrtamos |
||
---|---|---|---|
Ligne 1: | Ligne 1: | ||
====== Combinaisons ====== | ====== Combinaisons ====== | ||
+ | |||
+ | **//En construction// | ||
+ | |||
+ | ===== Objectif ===== | ||
+ | |||
+ | Exemple pour des combinaisons de 3 objets pris 2 à 2. Soit une liste d' | ||
+ | |||
+ | * on veut connaitre toutes les façons de les présenter 2 à 2, sans tenir compte de l' | ||
+ | |||
+ | * en tenant compte si nécessaires de répétitions | ||
+ | |||
+ | * et on veut savoir combien | ||
+ | |||
+ | NB: Contrairement aux permutations et aux arrangements, | ||
+ | |||
+ | \\ | ||
+ | Référence externe pour la définition: | ||
+ | |||
+ | ===== Nombre de combinaisons de n objets pris k à k ===== | ||
+ | |||
+ | Le calcul du nombre de combinaisons de n objets pris k à k est donnée par la formule: | ||
+ | |||
+ | < | ||
+ | |||
+ | Soit, en Python (si fact(n)=factorielle de n): | ||
+ | |||
+ | Cnk = fact(n)/ | ||
+ | |||
+ | ==== Solution non-récursive ==== | ||
+ | |||
+ | Puisque nous sommes sous Python qui sait calculer des nombres entiers énormes, nous pouvons utiliser brutalement les factorielles en prenant la fonction fact(n) présentée ailleurs sur ce site: | ||
+ | |||
+ | <code python> | ||
+ | combin = lambda n,k: fact(n)// | ||
+ | </ | ||
+ | |||
+ | Il est difficile de faire plus simple... | ||
+ | |||
+ | Mais le plus efficace est quand même le suivant, qui calcule progressivement le résultat sans calculer des grands nombres: | ||
+ | |||
+ | <code python> | ||
+ | def combin(n, k): | ||
+ | """ | ||
+ | if k > n//2: | ||
+ | k = n-k | ||
+ | x = 1 | ||
+ | y = 1 | ||
+ | i = n-k+1 | ||
+ | while i <= n: | ||
+ | x = (x*i)//y | ||
+ | y += 1 | ||
+ | i += 1 | ||
+ | return x | ||
+ | </ | ||
+ | |||
+ | Attention à ne pas utiliser dans ce code le raccourci habituel %%x *= i//y%%, car il faut que la multiplication ait lieu **avant** la division entière, sinon celle-ci ne tombe pas juste, et le résultat final est faux. | ||
+ | |||
+ | Pour que cette fonction soit utilisable avec de très grands nombres, j'ai tenu compte des points suivants: | ||
+ | |||
+ | * il faut utiliser while au lieu de for, car xrange() ne supporte pas les types ' | ||
+ | |||
+ | * si %%k> | ||
+ | |||
+ | |||
+ | Exemple d' | ||
+ | |||
+ | <code python> | ||
+ | print combin(3, | ||
+ | print combin(50, | ||
+ | </ | ||
+ | |||
+ | Ce code est très rapide: par exemple, le nombre de combinaisons de 100000 (cent mille) objets pris 100 à 100 donne en moins d' | ||
+ | |||
+ | < | ||
+ | 1019745118068508355816143175813864936340849501519143871777101229221311704707028464150801159851819171048218667041957300317610197834902713500555020173048960798706784754817501032954266210951200883251987669862675631847360156537041811409331039637713779759572781307343494274990819880664139309679429703675390591419799883108282575404188096902252124000 | ||
+ | </ | ||
+ | |||
+ | ==== Solution récursive ==== | ||
+ | |||
+ | La solution récursive est particulièrement simple: | ||
+ | |||
+ | <code python> | ||
+ | def combin(n, | ||
+ | """ | ||
+ | if k==0 or k==n: | ||
+ | return 1 | ||
+ | return combin(n-1, k-1) + combin(n-1, | ||
+ | </ | ||
+ | |||
+ | Malheureusement, | ||
+ | |||
+ | Pour mémoire, citons une version non-récursive qui imite assez bien la version récursive en gérant directement la pile des appels: | ||
+ | |||
+ | <code python> | ||
+ | def combin(n, k): | ||
+ | """ | ||
+ | pile = [[n,k]] | ||
+ | r = 0 | ||
+ | while len(pile)> | ||
+ | i, j = pile.pop(-1) | ||
+ | if j==0 or i==j: | ||
+ | r += 1 | ||
+ | else: | ||
+ | pile.append([i-1, | ||
+ | pile.append([i-1, | ||
+ | return r | ||
+ | </ | ||
+ | |||
+ | Le principe n'est pas très compliqué. On utilise une pile pour conserver les combinaisons en attente de résolution, | ||
+ | |||
+ | - initialement, | ||
+ | |||
+ | - A chaque boucle while, on dépile le dernier couple [i,j]. Les 2 seuls couples dont on connait la valeur correspondent à combin(x, | ||
+ | |||
+ | La boucle s' | ||
+ | |||
+ | Voyons cela sur un exemple. On ajoute juste après le while la ligne suivante (avec la bonne indentation): | ||
+ | |||
+ | <code python> | ||
+ | print r, pile | ||
+ | </ | ||
+ | |||
+ | et on lance l' | ||
+ | |||
+ | <code python> | ||
+ | print combin(5,3) | ||
+ | </ | ||
+ | |||
+ | Ce qui affiche: | ||
+ | |||
+ | < | ||
+ | 0 [[5, 3]] | ||
+ | 0 [[4, 2], [4, 3]] | ||
+ | 0 [[4, 2], [3, 2], [3, 3]] | ||
+ | 1 [[4, 2], [3, 2]] | ||
+ | 1 [[4, 2], [2, 1], [2, 2]] | ||
+ | 2 [[4, 2], [2, 1]] | ||
+ | 2 [[4, 2], [1, 0], [1, 1]] | ||
+ | 3 [[4, 2], [1, 0]] | ||
+ | 4 [[4, 2]] | ||
+ | 4 [[3, 1], [3, 2]] | ||
+ | 4 [[3, 1], [2, 1], [2, 2]] | ||
+ | 5 [[3, 1], [2, 1]] | ||
+ | 5 [[3, 1], [1, 0], [1, 1]] | ||
+ | 6 [[3, 1], [1, 0]] | ||
+ | 7 [[3, 1]] | ||
+ | 7 [[2, 0], [2, 1]] | ||
+ | 7 [[2, 0], [1, 0], [1, 1]] | ||
+ | 8 [[2, 0], [1, 0]] | ||
+ | 9 [[2, 0]] | ||
+ | 10 | ||
+ | </ | ||
+ | |||
+ | On voit bien que r n'est incrémentée (et la pile dépilée) que lorsque la combinaison finale est [0,x] ou [x,x]. | ||
+ | |||
+ | Le résultat final est ok, mais par rapport au code non-récursif précédent, | ||
+ | |||
+ | A ne conserver que pour des considérations théoriques. | ||
+ | |||
+ | ===== Liste des combinaisons d'une liste de n objets pris k à k ===== | ||
+ | |||
+ | Nous savons maintenant calculer le **nombre** de combinaisons, | ||
+ | |||
+ | Par exemple, nous voulons trouver la liste de toutes les combinaisons de de 3 objets %%[1,2,3]%% pris 2 à 2, qui est: %%[[1,2], [1,3], [2,3]]%% | ||
+ | |||
+ | Nous allons reprendre tout simplement le principe de " | ||
+ | |||
+ | Cela donnera comme code: | ||
+ | |||
+ | <code python> | ||
+ | def combinliste(seq, | ||
+ | p = [] | ||
+ | i, imax = 0, 2**len(seq)-1 | ||
+ | while i<=imax: | ||
+ | s = [] | ||
+ | j, jmax = 0, len(seq)-1 | ||
+ | while j<=jmax: | ||
+ | if (i>> | ||
+ | s.append(seq[j]) | ||
+ | j += 1 | ||
+ | if len(s)==k: | ||
+ | p.append(s) | ||
+ | i += 1 | ||
+ | return p | ||
+ | </ | ||
+ | |||
+ | Exemples d' | ||
+ | |||
+ | <code python> | ||
+ | print combinliste([' | ||
+ | print combinliste([' | ||
+ | </ | ||
+ | |||
+ | Comme vu précédemment, | ||
+ | |||
+ | ===== Liste des combinaisons d'une chaine de n caractères pris k à k ===== | ||
+ | |||
+ | C'est le même principe, à part que la donnée est une chaîne de n caractères, | ||
+ | |||
+ | La solution la plus simple est d' | ||
+ | |||
+ | <code python> | ||
+ | def combinchaine(ch, | ||
+ | return ['' | ||
+ | </ | ||
+ | |||
+ | Exemple d' | ||
+ | |||
+ | <code python> | ||
+ | print combinchaine(' | ||
+ | [' | ||
+ | </ | ||
+ | |||
+ | ===== Combinaisons de n objets distincts pris k à k avec répétition ===== | ||
+ | |||
+ | Pour la théorie, voir par exemple: [[http:// | ||
+ | |||
+ | ici, on a n objets distincts, mais on veut les voir répétés dans les résultats des combinaisons de ces n objets pris k à k. | ||
+ | |||
+ | Par exemple, [1,2,3] => %%[[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]]%% alors que sans répétitions: | ||
+ | |||
+ | ==== Nombre de combinaisons de n objets distincts pris k à k avec répétition ==== | ||
+ | |||
+ | Ce nombre est égal à la combinaison de (n+k-1) objets pris k à k. | ||
+ | |||
+ | D'ou la fonction: | ||
+ | |||
+ | <code python> | ||
+ | def combinrep(n, | ||
+ | """ | ||
+ | return combin(n+k-1, | ||
+ | </ | ||
+ | |||
+ | Exemple pour [1,2,3] pris 2 à 2 avec répétition: | ||
+ | |||
+ | <code python> | ||
+ | print combinrep(3, | ||
+ | 6 | ||
+ | </ | ||
+ | |||
+ | ==== Liste des combinaisons de n objets distincts pris k à k avec répétition ==== | ||
+ | |||
+ | On va utiliser la liste des combinaisons " | ||
+ | |||
+ | * expansion de la liste initiale, de sorte que chacun des n objets soit répété k fois. Par exemple pour [1,2,3] et k=2 => [1, | ||
+ | |||
+ | * calcul de la liste des combinaisons " | ||
+ | |||
+ | * élimination du résultat des redondances de sorte que chaque séquence n' | ||
+ | |||
+ | Ce qui donne comme code: | ||
+ | |||
+ | <code python> | ||
+ | def combinlisterep(seq, | ||
+ | """ | ||
+ | # ajoute chaque objet de seq pour quils apparaissent chacun k fois | ||
+ | seq2 = [] | ||
+ | for elem in seq: | ||
+ | if elem not in seq2: | ||
+ | for i in xrange(0, | ||
+ | seq2.append(elem) | ||
+ | # calcule la liste " | ||
+ | p = combinliste(seq2, | ||
+ | # élimine de cette liste les éléments identiques (comme [1,2] et [1,2]) | ||
+ | p2 = [] | ||
+ | for x in p: | ||
+ | if x not in p2: | ||
+ | p2.append(x) | ||
+ | # et renvoie le résultat | ||
+ | return p2 | ||
+ | </ | ||
+ | |||
+ | Exemple: | ||
+ | |||
+ | <code python> | ||
+ | print combinlisterep([1, | ||
+ | [[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]] | ||
+ | </ | ||
+ | |||
+ | ==== Liste des combinaisons d'une chaine de n caractères pris k à k avec répétition ==== | ||
+ | |||
+ | On va simplement utiliser la fonction précédente: | ||
+ | |||
+ | <code python> | ||
+ | def combinchainerep(ch, | ||
+ | return ['' | ||
+ | </ | ||
+ | |||
+ | Exemple: | ||
+ | |||
+ | <code python> | ||
+ | ch = ' | ||
+ | print combinchainerep(ch, | ||
+ | [' | ||
+ | </ | ||
+ | |||
+ | |||
+ | ===== Combinaisons de n objets non tous distincts pris k à k avec ou sans répétition ===== | ||
+ | |||
+ | Ici, on va généraliser tout ce qu'on a vu jusqu' | ||
+ | |||
+ | Par exemple, on a [1, | ||
+ | |||
+ | On voit qu'on a 8 objets, avec 5 objets distincts (le 2 apparait 2 fois, et le 4 apparait 3 fois). | ||
+ | |||
+ | Le fait que certains objets se retrouvent à plusieurs exemplaires va entrainer 2 conséquences dans la liste des résultats: | ||
+ | |||
+ | * présence de répétitions d' | ||
+ | |||
+ | * présence de répétition de séquences dans la liste. Exemple: [2,4,5] et [2,4,5] | ||
+ | |||
+ | On va donc créer du code pour, selon le problème à résoudre, supprimer des résultats l'une ou l' | ||
+ | |||
+ | Suppression de la liste résultat des séquences comportant des répétitions d' | ||
+ | |||
+ | <code python> | ||
+ | def sansrepetelem(p): | ||
+ | """ | ||
+ | r = [] | ||
+ | for seq in p: | ||
+ | for elem in seq: | ||
+ | ok = True | ||
+ | if seq.count(elem)!=1: | ||
+ | ok = False | ||
+ | break | ||
+ | if ok: | ||
+ | r.append(seq) | ||
+ | return r | ||
+ | </ | ||
+ | |||
+ | Exemple: | ||
+ | |||
+ | <code python> | ||
+ | p = [[1, | ||
+ | print sansrepetelem(p) | ||
+ | [[1, 2, 3]] | ||
+ | </ | ||
+ | |||
+ | Dans la liste résultat, suppression des séquences répétées de sorte que chaque séquence n' | ||
+ | |||
+ | <code python> | ||
+ | def sansrepetseq(p): | ||
+ | """ | ||
+ | r = [] | ||
+ | for seq in p: | ||
+ | if seq not in r: | ||
+ | r.append(seq) | ||
+ | return r | ||
+ | </ | ||
+ | |||
+ | Exemple: | ||
+ | |||
+ | <code python> | ||
+ | p = [[1, | ||
+ | print sansrepetseq(p) | ||
+ | [[1, 2, 4], [1, 2, 3]] | ||
+ | </ | ||
+ | |||
+ | On peut même supprimer les 2 type de répétitions d'un seul coup: | ||
+ | |||
+ | <code python> | ||
+ | def sansrepet(p): | ||
+ | return sansrepetelem(sansrepetseq(p)) | ||
+ | </ | ||
+ | |||
+ | On peut aussi vouloir calculer le nombre d' | ||
+ | |||
+ | <code python> | ||
+ | def elemdiff(seq): | ||
+ | """ | ||
+ | r = [] | ||
+ | for elem in seq: | ||
+ | if elem not in r: | ||
+ | r.append(elem) | ||
+ | return len(r) | ||
+ | </ | ||
+ | |||
+ | Reprenons maintenant notre exemple plus haut: seq=[1, | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | \\ | ||
+ | Amusez-vous bien! | ||