Algorithmique 1 Durée : 2h Machines électroniques interdites Tous documents papiers autorisés Il est conseillé de traiter les deux parties dans l ordre du sujet. Veuillez respecter les notations introduites dans l énoncé. Il est inutile de paraphraser l énoncé dans vos réponses, mais des explications avec dessins sur votre code sont les bienvenues. I - Listes doublement chaînées circulaires (12 points) type Position is private ; -- type des positions d éléments dans les listes doublement cha^ınées funct ion Creer return Position ; -- retourne une nouvelle position valide dont l élément -- doit ^etre initialisé en utilisant "ChangerElem" ci-dessous. function Elem(P: Position) return Element ; -- requiert: P position valide -- retourne l élément associé à la position P. procedure ChangerElem(P: Position; E: Element) ; -- requiert: P position valide -- garantit: Elem(P)=E. procedure Detruire( P: in out Position) ; -- requiert: P position valide. -- garantit: P détruite et invalide. -- si P figurait dans une liste, alors -- elle a été proprement retirée de cette liste. Fig. 1 Opérations sur les positions Dans cet exercice, on s intéresse à des listes doublement chaînées. L intérêt de cette structure de données est qu on peut retirer une cellule du chaînage à coût constant dès qu on a un pointeur sur cette cellule (alors que pour un chaînage simple il faut connaître un pointeur sur la cellule précédente dans le chaînage). 2009-2010 page : 1/6
Étant donné un type Element représentant les éléments mis dans les listes, on définit un paquetage ListesDC introduisant deux types abstraits : le type ListeDC des listes chaînées, et le type Position des positions dans les listes chaînées. Les opérations pour manipuler les positions sont spécifiées figure 1 page 1. Les opérations pour manipuler les listes sont spécifiées figure 2 page 4. Sous l hypothèse que le type Element est en fait le type Character, on donne un programme TestListesDC figure 3 page 4 qui illustre le comportement attendu des principales opérations. Concrètement, une position est simplement un pointeur sur une cellule du chaînage, avec les définitions ci-contre à droite. Le pointeur (resp. ) représente la position suivante (resp. précédente) dans la liste chaînée. type Position is access Cellule; type Cellule i s record : Element ;, : Position; end record ; Ainsi une position est valide au sens de la spécification figure 1 ssi c est un pointeur non nul et alloué dans le tas. Les sous-programmes Elem et ChangerElem spécifiés figure 1 sont trivialement implémentés par les définitions ci-dessous. function Elem(P: Position)... return P. ; procedure ChangerElem... P. := E ; Par ailleurs, toute position P correspondant à un pointeur alloué du tas vérifie l invariant suivant : P. est un pointeur alloué tel que P..=P. Pour programmer sur les positions, on s impose la discipline suivante : Ne jamais modifier directement les champs et des positions. Passer plutôt par l intermédiaire de la procédure Met ci-contre. procedure Met(Srce,Dest:Position) is --requiert: Srce et Dest valides Srce. := Dest ; Dest. := Srce ; En effet, l utilisation de cette fonction a l intérêt de rendre le code plus lisible et d établir l invariant pour la position Srce. Question 1 (2 points) : Cependant, l exécution de Met(Srce,Dest) où Srce et Dest sont des pointeurs alloués peut casser l invariant d une autre position valide. Donner un exemple. Question 2 (1,5 points) : Écrire la fonction Creer spécifiée à la figure 1 qui alloue une nouvelle position pointant sur elle-même comme dans le dessin de droite : l invariant de cette nouvelle position est donc trivialement établi. 2009-2010 page : 2/6
Une liste doublement chaînée est simplement codée par une position spéciale du chaînage appelée Senti (pour sentinelle). type ListeDC i s record Senti: Position ; end record ; Ainsi, une liste L: ListeDC est valide au sens de la spécification donnée figure 2 ssi L.Senti est une position valide. En outre, L.Senti. n a aucune signification et peut être quelconque. Enfin, si L.Senti.=L.Senti alors la liste est vide. Sinon, L.Senti. et L.Senti. sont deux positions allouées qui représentent respectivement la tête et la queue de la liste. liste vide liste contenant un seul élément u u liste à 2 éléments un liste à 3 éléments une u n u e n Question 3 (1,5 points) : Écrire les 3 procédures CreerVide, EstVide et Tete spécifiées à la figure 2. Question 4 (2 points) : Sous l hypothèse qu on dispose d une procédure Put(E: Element), écrire la procédure Afficher spécifiée figure 2 et illustrée figure 3. Dans les 2 questions suivantes, il faut exploiter la régularité du chaînage de manière à ce que le code du cas général fonctionne aussi pour les cas particuliers (position isolée, liste vide, etc). Question 5 (2 points) : Sous l hypothèse qu on a défini : procedure Liberer is new Ada.Unchecked_Deallocation(Cellule,Position); écrire la procédure Detruire spécifiée figure 1 et illustrée figure 3. La cellule initialement pointée par P doit être libérée. Question 6 (3 points) : Écrire la procédure InsererEnQueue spécifiée figure 2 et illustrée figure 3. 2009-2010 page : 3/6
type ListeDC is private; -- type des listes doublement cha^ınées function CreerVide return ListeDC ; -- retourne une liste vide et valide. function EstVide(L: ListeDC) return Boolean ; -- requiert: L valide -- retourne True ssi L est vide. procedure Afficher(L: ListeDC) ; -- requiert: L valide function Tete(L: ListeDC) return Position ; -- requiert: L valide et non EstVide(L) -- retourne une position valide correspondant à la t^ete de L procedure InsererEnQueue(L: ListeDC; P: Position) ; -- requiert: L et P valides -- garantit: P placée en queue de la liste L. -- si P figurait dans une autre liste, alors -- elle a été proprement retirée de cette liste. Fig. 2 Opérations sur les listes procedure TestListesDC is Pos: array(element range A.. G ) of Position ; L1, L2: ListeDC ; L1 := CreerVide ; for E in Pos Range loop Pos(E):= Creer ; ChangerElem(Pos(E),E) ; InsererEnQueue(L1,Pos(E)) ; end loop ; Afficher(L1) ; Detruire(Pos( C )) ; Afficher(L1) ; L2 := CreerVide ; InsererEnQueue(L2,Pos( B )) ; InsererEnQueue(L2,Pos( E )) ; InsererEnQueue(L2,Pos( G )) ; Afficher(L1) ; Afficher(L2) ; -- affichage attendu: ABCDEFG -- affichage attendu: ABDEFG -- affichage attendu: ADF -- affichage attendu: BEG Fig. 3 Exemple de programme utilisateur 2009-2010 page : 4/6
II - File de priorités de Dial (8 points) Cet exercice consiste à implémenter une file de priorités de Dial pouvant être utilisée notamment dans l algorithme de Dijkstra calculant des plus courts chemins dans un graphe orienté à coûts positifs. Connaître cet algorithme n est pas nécessaire pour réaliser cet exercice. Par contre, on utilise ici le paquetage ListesDC des listes doublement chaînées de l exercice précédent. Concrètement la file de priorités sert à conserver des éléments de type Element de manière triée en fonction du champ Prio, appelé priorité de l élément. type Element i s record Prio: Natural ; Nom: Character ; end record ; La file est définie ici comme l état interne d un paquetage appelé FilePrio. A l initialisation du paquetage, cet état interne correspond à la file vide. Ensuite, il est modifié via les opérations fournies ci-dessous : package FilePrio i s funct ion Taille return Natural ; -- retourne le nombre d éléments présents dans la file. procedure ExtraireMin(Min: out Element) ; -- requiert Taille > 0 -- garantit: "Min" était un élément de priorité minimum de la file -- qui a été détruit. En particulier, la position de cet élément -- n est plus valide! La taille de la file est décrémentée. type PositionFile is private ; -- type des positions d éléments procedure Inserer(E: Element; P: out PositionFile) ; -- garantit: E ajouté dans la file avec la position valide P. -- La taille de la file est incrémentée. procedure ModifierPriorite(P: PositionFile; Prio: Natural) ; -- requiert: P une position valide de la file. -- garantit: l élément de position P prend la priorité Prio. -- La taille de la file est inchangée. private type PositionFile is new ListesDC.Position ; end FilePrio ; Dans la suite, on suppose que DeltaP: constant Natural est une constante entière fixée. Par ailleurs, on définit un entier noté PrioDerMin dépendant de l historique des appels de procédures du paquetage : PrioDerMin vaut soit 0 s il n y a pas eu d appel à ExtraireMin, soit Min.Prio si Min est l élément ayant été extrait lors du dernier appel à ExtraireMin. Pour optimiser sa représentation interne, le paquetage FilePrio requiert que les programmes qui l utilisent vérifient une condition particulière appelée ici hypothèse de Dial (qui est en particulier vérifiée par l algorithme de Dijkstra) : Lors de tout appel à Inserer(E,P), on a E.Prio in PrioDerMin..PrioDerMin+DeltaP. Lors de tout appel à ModifierPriorite(P,Prio), Prio in PrioDerMin..PrioDerMin+DeltaP 2009-2010 page : 5/6
Par exemple, sous l hypothèse que DeltaP=5, alors l extrait de programme suivant vérifie l hypothèse de Dial : P1, P2, P3: PositionFile ; X: Element ; Inserer((5, A ),P1) ; Inserer((2, B ),P2) ; Inserer((4, C ),P3) ; ExtraireMin(X) ; -- X=(2, B ). P2 détruit. ModifierPriorite(P1,3) ; ExtraireMin(X) ; -- X=(3, A ). P1 détruit. ExtraireMin(X) ; -- X=(4, C ). P3 détruit. Inserer((9, D ),P1) ; Par contre, si dans cette dernière ligne, on change la priorité 9 soit en 10 soit en 3, alors l hypothèse de Dial est violée puisque le dernier minimum extrait était de priorité 4. Le principe de l implémentation est le suivant : la file est codée comme un tableau de listes doublement chaînées telles que tous les éléments d une même liste ont même priorité. Grâce à l hypothèse de Dial, à tout instant, il ne peut y avoir que DeltaP+1 priorités distinctes dans la file. Ainsi, les listes de la file sont mises dans le tableau File ci-dessous : subtype Indice i s Integer range 0.. DeltaP ; File: array(indice) of ListeDC ; Les éléments de priorité Prio se trouvent dans la liste d indice Prio mod File Length. Bien sûr, le type Element de la file est le même que celui utilisé dans le paquetage ListesDC. Pour coder les opérations efficacement, on introduit aussi les variables globales suivantes dans l implémentation du paquetage FilePrio : NbElems: Natural ; IndiceMin: Indice ; -- nombre d éléments présents dans la file. -- indice de la liste de priorité minimum. On a l invariant suivant : si NbElems est non nul, alors File(IndiceMin) est une liste non vide qui contient les éléments de priorité minimum de la file. Question 7 (1 point) : Écrire le bloc d instructions principal du paquetage FilePrio qui permet d initialiser son état à la file vide. Écrire aussi le code de la fonction Taille. Question 8 (4 points) : Écrire le code ExtraireMin en pensant bien à mettre-à-jour les variables globales du paquetage. Question 9 (3 points) : Le type PositionFile étant simplement défini comme un renommage 1 du type Position de la page 1, écrire les procédures Inserer et ModifierPriorite. 1 Ceci a simplement pour but d empêcher un utilisateur de la file de priorités de modifier directement la priorité d un élément en passant par ChangerElem au lieu de ModifierPriorite. 2009-2010 page : 6/6