2015/2016
Exercice 1 : Pile renversée Corrigé
Consignes Pour tous les exercices de cette planche on aura préalablement écrit l implémentation des piles à capacité limitée ou illimitée vue en cours, et l on ne s autorisera à n utiliser sur les piles que leurs seules primitives : creer pile(), depiler(), empiler(,), top(,), taille(), est vide() On travaillera au choix avec des piles à capacité limitée ou illimitée. (Indication : il est légèrement plus simple d utiliser des piles à capacité illimitée, on n a pas à s occuper de leur capacité). Le corrigé utilise les piles à capacité limitée ; il s adapte presque immédiatement aux piles à capacité illimitée en ignorant tout ce qui concerne la capacité des piles.
Exercice 1 : créer une pile renversée Exercice 1. 1. Ecrire une fonction renverse(pile) prenant en argument une pile et qui retourne la pile obtenue en inversant l ordre des éléments de pile. La pile pile pourra être vidée. 2. Même question, sauf qu à la fin l argument pile doit être inchangé.
Exercice 1 : créer une pile renversée - a) On s autorise à vider pile. def renverse(pile): n = taille(pile) pile2 = creer pile(n) # supprimer n pour une pile illimitée while not(est vide(pile2)): empiler(pile2,depiler(pile)) return pile2
Exercice 1 : créer une pile renversée - b) A la fin pile doit être inchangée. On stocke les résultats dépilés dans une pile tampon, que l on utilisera à la fin pour reconstituer le contenu de pile. def renverse1(pile): n = taille(pile) pile2 = creer pile(n) pile3 = creer pile(n) # pour stoker les éléments dépilés while not(est vide(pile)): lu = depiler(pile) empiler(pile2,lu) empiler(pile3,lu) while not(est vide(pile3)): # Reremplir pile empiler(pile,depiler(pile3)) return pile2
Exercice 2. Ecrire une fonction supprime(pile,p) qui supprime dans la pile pile son p-ième élément compté en partant du sommet (indication : il faudra utiliser une deuxième pile). Si p dépasse la taille de la pile la fontion retournera False, et sinon retournera True.
- On utilise une deuxième pile pour stocker les p 1 premiers éléments dépilés avant de supprimer le p-ième élément puis de rempiler ces p 1 éléments. def suprime(pile,p): if taille(pile) < p: return False pile2 = creer pile(p-1) # supprimer p-1 si pile illimitée for i in range(p-1): empiler(pile2,depiler(pile)) depiler(pile) for i in range(p-1): empiler(pile,depiler(pile2))) return True
Exercice 3. Ecrire une fonction echange(pile,p,q) qui échange dans la pile pile ses p-ième et q-ième éléments, comptés en partant du sommet. Si p ou q dépasse la taille de la pile la fonction retournera False, et sinon retournera True.
- def echange(pile,p,q): assert isinstance(p,int) and isinstance(q,int) if taille(pile)<p or taille(pile)<q: # Cas d impossibilité return False if p == q: return True # Ici rien à faire if p > q: # pour que p soit inférieur à q p,q = q,p pile2 = creer pile(p) pile3 = creer pile(q-p) for i in range(p): empiler(pile2,depiler(pile)) for i in range(q-p): empiler(pile3,depiler(pile)) empiler(pile,depiler(pile2)) empiler(pile2,depiler(pile3)) for i in range(q-p-1): empiler(pile,depiler(pile3)) for i in range(p): empiler(pile,depiler(pile2)) return True
Adapter l algorithme vu en cours pour déterminer si un mot est bien parenthésé pour englober les cas des 3 types de parenthèses (), [], {}.
- avec 3 + 1 piles def parenthesage(mot): pile1 = creer pile(len(mot)) # création de la pile () pile2 = creer pile(len(mot)) # création de la pile [] pile3 = creer pile(len(mot)) # création de la pile {} pile0 = creer pile(len(mot)) # création de la pile mémoire" for char in mot: # Parcours du mot if char == ( : # Si ( empiler(pile1,0); empiler(pile0,1) # on empile pile1 et on mémorise elif char == [ : # Si [ empiler(pile2,0); empiler(pile0,2) # on empile pile2 et on mémorise elif char == { : # Si { empiler(pile3,0); empiler(pile0,3) # on empile pile3 et on mémorise elif char == ) : # Si ) if est vide(pile1) or depiler(pile0)!= 1: return False else: depiler(pile1) elif char == ] : # Si ] if est vide(pile2) or depiler(pile0)!= 2: return False else: depiler(pile2) elif char == } : # Si } if est vide(pile3) or depiler(pile0)!= 3: return False else: depiler(pile3) return est vide(pile1) and est vide(pile2) and est vide(pile3)
- avec 1 pile Ou encore utiliser une seule liste et empiler les différentes parenthèses ouvrantes : def parenthesage(mot): pile = creer pile(len(mot)) # création de la pile for char in mot: # Parcours du mot if char == ( : # Si ( empiler(pile, ( ) # on empile ( elif char == [ : # Si [ empiler(pile, [ ) # on empile [ elif char == { : # Si { empiler(pile, { ) # on empile { elif char == ) : # Si ) if est vide(pile) or depiler(pile)!= ( : return False elif char == ] : # Si ] if est vide(pile) or depiler(pile)!= [ : return False elif char == } : # Si } if est vide(pile) or depiler(pile)!= { : return False return est vide(pile) Cette approche est moins pratique que la précédente si l on souhaite adapter le programme pour qu il retourne aussi les positions des différentes parenthèses.
Exercice 7. Exercice 4. On se bornera aux seules parenthèses ( et ). 1. Adapter l algorithme vu en cours pour tester si une chaine est bien parenthésée, de sorte que : il retourne False pour une chaine non correctement parenthésée, il retourne la position des parenthèses pour une chaine bien parenthésée ; par exemple avec pour argument 2*(1+(3-1)) l algorithme retournera la liste : [(6,10),(3,11)].
Exercice 5. On adapte le programme vu en cours pour qu il empile les positions des parenthèses ouvrantes rencontrées. def parentheses(mot): pile = creer pile(len(mot)) result = [ ] for i in range(len(mot)): if mot[i] == ( : empiler(pile, i+1) elif mot[i] == ) : if est vide(pile): return False result.append((depiler(pile), i+1)) if est vide(pile): return result else: return False
Exercice 5. : 2ème version Plutôt que de parcourir la séquence par indice on peut mettre à profit l instruction : for i, c in enumerate(seq) qui implémente une boucle for où : c parcourt les éléments de la séquence seq, tandis que i parcourt les indices correspondants. def parentheses(mot): pile = creer pile(len(mot)) result = [ ] for i, c in enumerate(mot): if c == ( : empiler(pile,i+1) elif c == ) : if est vide(pile): return False result.append((depiler(pile),i+1)) if est vide(pile): return result else: return False
Corrigé Exercice 6. Résolution d un labyrinthe On se donnera un labyrinthe carré par un tableau carré de type ndarray (numpy). Un 0 symbolisera un passage, un 1 un mur. On prendra par exemple : import numpy as np T = np.array([ [0,0,1,0,1,0], [0,1,1,0,0,0], [0,0,0,0,1,1], pour : [1,1,1,0,1,0], [0,0,1,0,0,0], [0,0,0,0,1,0] ]) De chaque case on peut se déplacer sur une case libre voisine horizontalement ou verticalement. Entrée et sortie du labyrinthe seront des cases données par leurs indices, par exemple (0, 0) et (5, 5).
Corrigé Exercice 6. Résolution d un labyrinthe Le but de l exercice est d écrire une fonction qui détermine un chemin de l entrée à la sortie du labyrinthe par une recherche en profondeur utilisant une pile. Le principe est : Chaque case sera symbolisée par le couple (i,j) de ses indices dans le tableau. Lorsqu une case aura été déjà visité, sa valeur dans le tableau sera mise à -1. Une fonction voisin() prendra en paramètre le tableau et les indices d une case et retournera la liste de ses cases voisines libres et non déjà visitées. De chaque case on se déplacera sur une telle case voisine libre et non déjà visitée. Une pile à capacité illimitée constituera le fil d ariane : on empilera les cases visitées successivement. Dès qu une case n aura plus aucune case voisine non déjà visitée on la dépilera, pour poursuivre le trajet à partir de la case précédente.
Corrigé Exercice 6. Résolution d un labyrinthe 1. Ecrire la fonction voisin() comme décrite ci-dessus. 2. Ecrire la fonction trajet() prenant en paramètre le tableau du labyrinthe, et les couples d indices de l entrée et de la sortie, et qui retourne la pile des cases successivement visitées constituant un trajet de l entrée à la sortie. 3. La tester sur le tableau ci-dessus avec pour entrée (0,0) et sortie (5,5).
Corrigé Exercice 6. Résolution d un labyrinthe On utilise des piles à capacités illimitées. 1) def voisins(t,v): V = [] N = np.shape(t)[0] i,j = v[0],v[1] for a in (-1,1): if 0<= i+a <N: if T[i+a,j] == 0: V.append((i+a,j)) if 0<= j+a <N: if T[i,j+a] == 0: V.append((i,j+a)) return V
Corrigé Exercice 6. Résolution d un labyrinthe from copy import deepcopy def labyrinthe(t,entree,sortie): T = deepcopy(t) P = creer pile() Recherche = True v = entree empiler(p,v) while Recherche: vois = voisins(t,v) if vois == []: depiler(p) if est vide(p): return False v = top(p) else: i,j = vois[0] T[i,j] = -1 empiler(p,(i,j)) v = (i,j) if v == sortie: Recherche = False return P
Corrigé Exercice 6. Résolution d un labyrinthe In [1]: labyrinthe(t,(0,0),(5,5)) Out[1]: [(0, 0), (1, 0), (2, 0), (2, 1), (2, 2), (2, 3), (3, 3), (4, 3), (4, 4), (4, 5), (5, 5)] import numpy as np T = np.array([ [0,0,1,0,1,0], [0,1,1,0,0,0], [0,0,0,0,1,1], [1,1,1,0,1,0], [0,0,1,0,0,0], [0,0,0,0,1,0] ]) pour :
Annexe. 1. Montrer qu une chaine de caractère est bien parenthésée si et seulement si : le nombre de parenthèses ouvrantes ( égale le nombre de parenthèses fermantes ), et Dans chacun de ses préfixes le nombre de parenthèses ouvrantes est supérieur ou égal au nombre de parenthèses fermantes. On pourra procéder par récurrence forte sur la longueur de la chaine. 2. En déduire la correction de l algorithme vu en cours pour déterminer si une chaine de caractère est ou non bien parenthésée.
Annexe. 1. Montrons par récurrence forte sur la longueur de la chaine que : une chaine de caractère est bien parenthésée si et seulement si : (Propriété (P)) : (Propriété (P1)) : le nombre de parenthèse ouvrante égale le nombre de parenthèse ouvrante, et (Propriété (P2)) : dans chacun de ses préfixes le nombre de parenthèses ouvrantes est supérieur ou égal au nombre de parenthèses fermantes. Rappelons qu avec les parenthèses ( et ) un mot est bien parenthésé si : 1. Il ne contient aucune parenthèse ( ou ), ou 2. Il est de la forme (m) avec m un mot bien parenthésé, ou 3. Il est la concaténation m1.m2 de 2 mots m1, m2 bien parenthésés. (plutôt que de chaine de caractère, on parlera de mot.)
Annexe. Initialisation. Le mot vide ε est bien parenthésé, et vérifie (P) puisqu il ne contient aucune parenthèse, de même que tous ses préfixes (c est à dire lui-même ε). L équivalence est trivialement vérifiée pour le mot de longueur nulle. Hérédité. Soit un entier N > 0. Supposons que l équivalence est vérifiée pour tout mot de longueur < N. On montre séparément les deux implications et. Implication directe. Soit w un mot de longueur N bien parenthésé. 1er cas : si w ne contient aucune parenthèse. Alors il en est de même de tous ses préfixes, et trivialement w vérifie la propriété (P). Ce cas est réglé. 2ème cas : Si w est de la forme w = (u) avec u un mot bien parenthésé de longueur N 2. Par hypothèse de récurrence u contient autant de ( que de ). Or w contient 1 ( de plus et 1 ) de plus que u, ainsi w contient autant de ( que de ) : w vérifie (P1).
Si w est de la forme w = (u) avec u bien parenthésé. Soit w 0 un préfixe de w. Alors soit : w 0 est le mot vide ε, et ne contient aucune parenthèse, soit w 0 = (.u 0 où u 0 est un préfixe de u. Par hypothèse de récurrence u 0 contient au moins autant de ( que de ). Or w 0 = (.u 0 contient exactement 1 ( de plus que u 0 et autant de ). Ainsi w vérifie aussi la propriété (P2). Cela montre que lorsque w = (u) avec u bien parenthésé, alors w vérifie (P). 3ème cas. si w = u.v avec u et v deux mots de longueur non-nulle bien parenthésés. Alors u et v sont de longueur < N, et par hypothèse de recurrence vérifient (P). En particulier u et v contiennent tous deux autant de ( que de ), et donc il en est de même de w : w vérifie (P1). Montrons que w vérifie aussi (P2). Soit w 0 un préfixe de w. Deux cas sont possibles : w 0 est un préfixe de u, et donc, puisque u vérifie (P), il contient au moins autant de ( que de ). w 0 = u.v 0 où v 0 est un préfixe de v. Puisque v 0 contient au moins autant de ( que de ) tandis que u contient autant des 2, alors w 0 contient au moins autant de ( que de ). Ainsi w vérifie (P2) et donc (P). Ceci conclut la preuve de l implication directe.
Implication réciproque. Soit w un mot de longueur N vérifiant la propriété (P) ; il s agit de montrer que w est bien parenthésé. 1er cas. Si w ne débute pas par une parenthèse, w = c.w où c est un caractère autre que ( ou ). Alors w est de longueur N 1 et vérifie aussi (P). Par (HR), w est bien parenthésé, et par définition c aussi, et donc, toujours par définition, leur concaténation w est bien parenthésée. 2ème cas. w =).w est impossible : cela contredirait le fait que w vérifie (P) : en effet (P2) serait mis en défaut par le préfixe ). 3ème cas. Si w = (.w. Comme ci-dessus, si w ne se termine pas par une parenthèse alors on conclut aisément que w est bien parenthésé. De plus w ne peut pas se terminer par ( car puisque w contient autant de ( que de ) alors le préfixe de longueur N 1 de w contiendrait 1 ) de plus que de ( et contredirait le fait que w vérifie (P2).
Ainsi il ne reste plus qu à considérer le cas où w = (.u.). Puisque w vérifie (P1) alors u aussi. Soit u 0 un préfixe de u = u 0.u 1. Alors (.u 0 est un préfixe de w et contient donc au moins autant de ( que de ). Soit : u 0 contient au moins autant de ( que de ), soit : (.u 0 contient autant de ( que de ). Dans ce dernier cas : (.u 0 et u 1.) contiennent autant de ( que de ) et vérifient donc (P). Par (HR) (.u 0 et u 1.) sont bien parenthésés et par définition w, concaténation des deux, est bien parenthésé. On a la conclusion souhaitée. Ainsi soit w est bien parenthésé comme concaténation de deux mots bien parenthésés, soit tout préfixe de u contient au moins autant de ( que de ), c est à dire u vérifie (P 2), et donc (P). Par (HR) u est bien parenthésé. Alors par définition w = (u) l est aussi. Dans tous les cas w est bien parenthésé, ce qui prouve la réciproque et conclut la preuve.
Annexe. du programme def parenthesage(mot): compteur = 0 for char in mot: if char == ( : compteur += 1 elif char == ) : if compteur == 0: return False else: compteur -= 1 return (compteur == 0) # Parcours du mot # Si parenthèse ouvrante # on incrémente le compteur # Si parenthèse fermante # Et si aucun ( rencontré # Alors mal parenthésé # Sinon décrémentation # retourne True si le compteur=0 Un boucle for s achève toujours, il suffit donc de vérifier que le programme retourne le résultat attendu. Chaque passage dans la boucle correspond à l un des préfixes de mot. Et la condition compteur > 0 équivaut à avoir rencontré au moins autant de ( que de ) dans ce préfixe. Ainsi la sortie de la boucle sans retourner False équivaut à la condition (P2). La condition finale compteur == 0 correspond quant à elle à (P1). Donc l algorithme retourne True si et seulement si le mot vérifie (P), c est à dire s il est bien parenthésé.