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 Prochaine révision Les deux révisions suivantes | ||
combinaisons [2010/01/09 08:47] tyrtamos |
combinaisons [2010/01/10 07:31] 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 avec répétition ===== | ||
+ | |||
+ | Jusqu' | ||
+ | |||
+ | Quand l'un des éléments au moins apparait plusieurs fois dans la liste des objets, il y a deux effets dans la liste des combinaisons. | ||
+ | |||
+ | Par exemple dans les combinaisons de [1,2,2] pris 2 à 2 qui donne %%[[1, 2], [1, 2], [2, 2]]%%: | ||
+ | |||
+ | * présence de séquences contenant des répétitions. Ici: [2,2] | ||
+ | |||
+ | * présence de séquences identiques. Ici: [1,2] et [1,2]. | ||
+ | |||
+ | Que peut-on faire avec ça? Tout dépendra de ce qu'on obtenir par rapport au problème à résoudre: | ||
+ | |||
+ | * on n'a pas de répétition dans la liste des objets de départ mais on en veut dans les résultats | ||
+ | |||
+ | * on a des répétitions dans la liste des objets de départ et on veut éliminer ses effets dans les résultats | ||
+ | |||
+ | Voyons ces 2 cas. | ||
+ | |||
+ | **__On veut des combinaisons avec répétition__** | ||
+ | |||
+ | Par exemple: on a [1,2,3], mais on veut obtenir une combinaison de ces 3 objets pris 2 à 2 **avec répétition** comme: %%[[1, 1], [1, 2], [2, 2], [1, 3], [2, 3], [3, 3]]%% | ||
+ | |||
+ | Il suffit de transformer la liste de départ en répétant chaque élément autant de fois que la combinaison cherchée le demande (ici, 2): [1,2,3] devient donc [1, | ||
+ | |||
+ | Combien cela va-t-il donner de séquence dans les résultats? Cela en donnera: | ||
+ | |||
+ | < | ||
+ | |||
+ | n étant le nombre d' | ||
+ | |||
+ | <code python> | ||
+ | Combin(3+2-1, | ||
+ | 6 | ||
+ | </ | ||
+ | |||
+ | Pour calculer la liste de ces combinaisons avec répétition, | ||
+ | |||
+ | <code python> | ||
+ | def repet(seq, m=-1): | ||
+ | """ | ||
+ | si m==0, renvoie seq sans rien changer | ||
+ | si m==-1 (défaut), chaque élément de seq apparait len(seq) fois | ||
+ | """ | ||
+ | if m == 0: | ||
+ | return seq[:] | ||
+ | if m == -1: | ||
+ | m = len(seq) | ||
+ | r = [] | ||
+ | for elem in seq: | ||
+ | if elem not in r: | ||
+ | for k in xrange(0, | ||
+ | r.append(elem) | ||
+ | return r | ||
+ | </ | ||
+ | |||
+ | Avec cette fonction, la liste d' | ||
+ | |||
+ | On peut faire la même chose avec une chaine de caractère. Par exemple, on veut la liste des mots de 3 lettres de l' | ||
+ | |||
+ | <code python> | ||
+ | ch = '' | ||
+ | </ | ||
+ | |||
+ | |||
+ | |||
+ | \\ | ||
+ | Amusez-vous bien! | ||